当redis“缓存“遇上“击穿“


前言

最近在学Redis 想象一下,当你的应用程序需要处理大量的请求时,Redis就像是一位快递小哥,把数据飞快地送到用户手中。

用Redis不学其原理,就像四大名著不看红楼梦,说明这个人文学造诣和自我修养不足,他理解不了这种内在的阳春白雪的高雅艺术,他只能看到外表的辞藻堆砌,参不透其中深奥的精神内核,他整个人的层次就卡在这里了,只能度过一个相对失败的人生。


一、什么是缓存击穿?

在我们的业务中,经常会出现一些数据被频繁地访问 的情况,例如在秒杀活动中。这些被频繁访问的数据被称为热点数据。当缓存中的某个热点数据过期 时,如果大量的请求 访问该热点数据,由于无法从缓存中读取,这些请求将直接访问 数据库。然而,由于高并发的请求量,数据库很容易被冲垮,这就是缓存击穿。


二、解决方案

有两种常见的解决方案:

  • 互斥锁 :重视数据一致性 ,在后续学习分布式中能更深有体会。
  • 逻辑过期 :(从前有个火之国的木叶隐村有个叫"火影"的大牛,懒得做的事会让一众手下的忍者去处理,多好啊!)

三、实现

互斥锁(Mutex) :引用Redis官方文档:https://redis.io/commands/setnx/的Example (和React一样体验感拉满了)来简单说明一下互斥锁:

c 复制代码
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> 

可谓一山不容二虎 SETNX 我个人喜欢把它作为互斥锁的抽象概念(抽象的概念在《深入理解计算机系统》中被反复强调!!!)。 好吧,我是在拿它挡刀。不过可以确定的是:mykey在已经有value的情况下,禁止你再把新值放进去了!!! 直接上代码:

java 复制代码
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 获取锁
     * @param key
     * @return
     */
    public boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "任意值", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    /**
     * 释放锁
     *
     * @param key
     */
    public void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
    @Override
    public Result queryById(Long id) {
        Shop shop = queryWithMutex(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        return Result.ok();
    }
    /**
     * 互斥锁
     *
     * @param id
     * @return
     */
    public Shop queryWithMutex(Long id) {
        //1.获取缓存
        String key = CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在则直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否为空字符串
        if ("".equals(shopJson)) {
            return null;
        }
        //4.实现缓存重建
        //4.1获取互斥锁
        String lockKey = "lock:shop" + id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            //4.2判断是否获取成功
            if (!isLock) {
                //4.3失败,则休眠并重试
                Thread.sleep(50);
                queryWithMutex(id);
            }
            //进行DoubleCheck,再次检查换缓存是否存在,若存在则无需构建缓存(重复1 2步即可)
            key = CACHE_SHOP_KEY + id;
            shopJson = stringRedisTemplate.opsForValue().get(key);
            if (StrUtil.isNotBlank(shopJson)) {
                //3.存在则直接返回
                return JSONUtil.toBean(shopJson, Shop.class);
            }
            //4.4成功,根据id查询数据库
            shop = getById(id);
            //模拟重建的演示
            Thread.sleep(200);
            //5.不存在,返回错误
            if (shop == null) {
                //将空值写入Redis
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                //返回错误信息
                return null;
            }
            //6.存在,写入redis
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁(不管缓存构建过程中有无异常,都得解锁)
            unlock(lockKey);
        }
        //8.返回
        return shop;
    }
}

压测成功!2k 次请求只查了一次数据库,锁生效了! 查看redis缓存确实存进去了,

由于互斥锁适合处理高并发的场景,逻辑过期适合处理逻辑较为复杂的业务场景,等找到了一个合适的复杂业务再来介绍o(╥﹏╥)o


扯皮:

最近听到S.H.E的<<你曾是少年>>,哎呦不错哦~

你我來自湖北 四川 廣西 寧夏 河南 山東 貴州 雲南的小鎮鄉村 曾經發誓 要做了不起的人 卻在北京 上海 廣州 深圳 某天夜半忽然醒來 像被命運叫醒了 它說你不能就這樣過完一生

相关推荐
姜鱼问生6 小时前
获取的全是 172 开头的假 IP?Nginx 反代后,Flask 怎么才能拿到真实访客 IP?
后端
小蒜学长6 小时前
基于过程性评价的ai助学平台(代码+数据库+LW)
java·spring boot·后端·微信小程序·过程性评价
陈随易6 小时前
副业搞钱点子之 Umami 统计如何赚钱
前端·后端·程序员
利威尔xu7 小时前
万字详解 | Go 进阶(二)GC 三色标记:STW 从 10ms 压到 40μs,1 次 Full GC 让线上 P99 抖了 3 秒
开发语言·后端·性能优化·golang·高并发
金銀銅鐵7 小时前
[Java] 用 CLI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
tedcloud1238 小时前
Docker Compose 部署 Memos:从安装到备份恢复
linux·开发语言·jvm·后端·docker·容器·rust
木井巳8 小时前
【JavaEE】SpringBoot 统一功能处理: 拦截器、统一返回与全局异常
java·spring boot·后端·spring·java-ee
pan_junbiao8 小时前
Go语言中的空白标识符
开发语言·后端·golang
IT_陈寒9 小时前
SpringBoot性能优化完全指南
前端·人工智能·后端
随性而行3609 小时前
企业微信 Webhook 接收不到消息?网络连通性、URL 签名验签与 API 返回错误码排查指南
java·网络·人工智能·后端·微信·企业微信