[Redis-实战] 企业常用的缓存使用方案(查询、更新、击穿、穿透、雪崩) 附源码
创始人
2024-03-17 10:13:14

目录

🍊 缓存查询策略

🍩 缓存更新策略

🍭 缓存穿透

🍣 缓存雪崩

🍕 缓存击穿

👾 项目源码下载​​​​​​​


🍊 缓存查询策略

我们要查询的业务数据并不是经常改变的, 这里我们可以放到Redis缓存中, 降低对数据库的请求

下面我们以查询店铺为例, 因为店铺列表是不经常改变的数据, 所以我们可以请求redis缓存来降低MySQL的查询压力 

 @Overridepublic Result queryShopById(Long id) {//1.从Redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get("cache:shop" + id);//2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//3.存在, 直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}//4.不存在, 根据id查询数据库Shop shop = getById(id);//不存在, 返回错误提示信息if (shop == null){return Result.fail("店铺不存在!");}//存在, 写入redis中stringRedisTemplate.opsForValue().set("cache:shop" + id, JSONUtil.toJsonStr(shop));return Result.ok(shop);}

🍩 缓存更新策略

在常规的企业开发中,我们优先选择的缓存策略是 更新数据库的同时也会去更新缓存

在此情况下我们也要考虑三点 : 

1. 更新数据库后再删除缓存, 再查询的时候重新添加缓存 (这样可以保证数据查询的是最新的)

2.在单体项目中, 将缓存与数据库操作放在同一个事务中, 这样方便回滚. 分布式项目中需要使用分布式事务

3. 在并发场景下, 应当先操作数据库,再删除缓存

    @Override@Transactionalpublic void updateShop(Shop shop) {if (shop.getId() == null) {throw new RuntimeException("ID不能为null");}//1. 先更新数据库updateById(shop);//2. 后删除缓存stringRedisTemplate.delete("cache:shop" + shop.getId());}

🍭 缓存穿透

缓存穿透场景 : 假设用户恶意请求的数据在Redis和MySQL中均不存在, 导致Redis中的缓存不生效从而一直去请求MySQL

解决方案 : 因为用户传来的恶意数据在缓存和数据库中都不存在, 在从数据库中查询不到后将恶意数据缓存在Redis中

代码实现如下, 当在数据库没有查询到后, 将空信("")息存入到Redis中,并设置过期时间为2分钟, 当用户再次查询时, 校验如果为("") 直接返回 店铺信息不存在!

    @Overridepublic Result queryShopById(Long id) {//1.从Redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get("cache:shop" + id);//2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//3.存在, 直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}//判断命中的是否是空值if (Objects.equals(shopJson, "")) {//返回错误信息return Result.fail("店铺信息不存在!");}//4.不存在, 根据id查询数据库Shop shop = getById(id);//不存在, 返回错误提示信息if (shop == null){//将空值写入Redis中 并将有效期时间改为2分钟stringRedisTemplate.opsForValue().set("cache:shop" + id, "", 2L, TimeUnit.MINUTES);//返回错误信息return Result.fail("店铺不存在!");}//存在, 写入redis中 设置过期时间为30分钟stringRedisTemplate.opsForValue().set("cache:shop" + id, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);return Result.ok(shop);}

🍣 缓存雪崩

Redis的缓存雪崩意思是指: 在统一同一时间内Redis中的大量的Key失效, 导致请求压力到达数据库

解决办法 : 缓存数据的过期时间设置随机,将不同的Key的TTL设置随机值

🍕 缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的ky突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

🍥 解决方案1 : 互斥锁

锁代码实现(获取锁, 释放锁)

    /*** 获取锁** @param key* @return*/private boolean tryLock(String key) {//相当于 SETNX:添加一个String类型的键值对,当key不存在的时候执行Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);// BooleanUtil可以帮你自动拆装箱解决可能空指针问题return BooleanUtil.isTrue(flag);}/*** 释放锁** @param key*/private void unlock(String key) {stringRedisTemplate.delete(key);}

业务代码实现

这里的互斥锁如果获取不到锁就会进入休眠状态, 然后再去重新获取锁, 这样做能保持数据的一致性

 /*** 缓存击穿*/public Shop queryWithmutex(Long id){//1.从Redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get("cache:shop" + id);//2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//3.存在, 直接返回return JSONUtil.toBean(shopJson, Shop.class);}//判断命中的是否是空值if (Objects.equals(shopJson, "")) {//返回错误信息log.error("店铺信息不存在");return null;}//4实现缓存重建//4.1 获取互斥锁String lockKey = "lock:shop" + id;Shop shop;try {//这块功能的业务是, 当A线程操作该方法是, B线程进来判断锁是否释放, 如果没有释放则休眠重试, 为了解决数据一致性的问题boolean isLock = tryLock(lockKey);//4.2判断锁是否获取成功if (!isLock){//4.3 失败,则休眠重试Thread.sleep(50);//递归重试(这块地方有异议 不建议递归, 后期用到类似业务可以寻找其他解决办法)return queryWithmutex(id);}//根据id查询数据库shop = getById(id);if (shop == null) {//不存在 将空值写入Redis中 并将有效期时间改为2分钟 (这里是为了解决缓存穿透问题)stringRedisTemplate.opsForValue().set("cache:shop" + id, "", 2L, TimeUnit.MINUTES);//返回错误信息log.error("店铺信息不存在");return null;}//存在, 写入redis中 设置过期时间为30分钟stringRedisTemplate.opsForValue().set("cache:shop" + id, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);}catch (InterruptedException e){throw new RuntimeException(e);}finally {//7. 释放互斥锁unlock(lockKey);}return (shop);}

解决方案2 : 逻辑过期

设置逻辑过期数据和过期时间

    /*** 向Redis中写入店铺信息并设置逻辑过期时间* @param id* @param expireSeconds*/public void saveShop2Redis(Long id ,Long expireSeconds){//1.查询店铺数据Shop shop = getById(id);//2.封装逻辑过期时间RedisData redisData = new RedisData();redisData.setData(shop);//设置过期时间秒 测试时设置的时间短一些方便测试过期redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));//3.写入RedisstringRedisTemplate.opsForValue().set("cache:shop" + id, JSONUtil.toJsonStr(redisData));}

手动加入逻辑过期数据

    @Testvoid test(){//模拟后台管理手动设置热点数据shopService.saveShop2Redis(1L, 10L);}

 逻辑过期业务代码

    /*** 逻辑过期* @param id* @return*/public Shop queryWithLogicalExpire(Long id) {//1.从Redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get("cache:shop" + id);//2.判断是否存在if (StrUtil.isBlank(shopJson)) {//3.不存在, 直接返回return null;}//4. 命中, 需要把JSON反序列化为对象log.info("打桩数据 : {}", shopJson);RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);//反序列化Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);//获取过期时间LocalDateTime expireTime = redisData.getExpireTime();//5.判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {//5.1未过期, 直接返回店铺信息return shop;}//5.2已过期,需要缓存重建//6.缓冲重建//6.1获取互斥锁String lockKey = "lock:shop" + id;boolean isLock = tryLock(lockKey);//6.2判断锁是否获取成功if (isLock) {//TODO 6.3成功, 开启独立线程, 实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {//重建缓存log.info("开始缓存重建");this.saveShop2Redis(id, 20L);} catch (Exception e) {throw new RuntimeException(e);}finally {//释放锁log.info("释放锁");unlock(lockKey);}});}//6.4 返回过期的商铺信息return shop;}

这里实现的互斥锁, 如果没有拿到锁就会直接return返回历史数据, 在并发环境下短期内会造成数据的不一致性

比如我修改了name属性字段

 


👾 项目源码下载

扫描下方公众号二维码 回复: Redis缓存实战 即可领取项目源码 👇👇👇

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...