当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的<<你曾是少年>>,哎呦不错哦~

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

相关推荐
烈风1 小时前
004 Rust控制台打印输出
开发语言·后端·rust
用户21411832636021 小时前
用 AI 一键搞定!中医药科普短视频制作升级版
后端
秋难降2 小时前
零基础学习SQL(十一):SQL 索引结构|从 B+Tree 到 Hash,面试常问的 “为啥选 B+Tree” 有答案了
数据库·后端·mysql
SamDeepThinking2 小时前
用设计模式重构核心业务代码的一次实战
java·后端·设计模式
用户49055816081254 小时前
lvs会话同步
后端
用户49055816081254 小时前
linux内核网络协议栈报文的处理过程
后端
夜宵饽饽4 小时前
上下文工程实践 - 工具管理(上篇)
javascript·后端
ERP老兵_冷溪虎山4 小时前
Python/JS/Go/Java同步学习(第十三篇)四语言“字符串转码解码“对照表: 财务“小南“纸式转码术处理凭证乱码崩溃(附源码/截图/参数表/避坑指南)
java·后端·python
努力的小郑4 小时前
MySQL索引(四):深入剖析索引失效的原因与优化方案
后端·mysql·性能优化