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

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

相关推荐
utmhikari25 分钟前
【日常随笔】深入回答纯Vibe Coding写后端项目的几个问题
后端·ai编程·vibecoding
尚早立志35 分钟前
Spring Boot 源码研读之ConfigurableEnvironment 环境准备
java·spring boot·后端
布朗克1681 小时前
Go 入门到精通-08-复合类型之数组与切片
开发语言·后端·golang·数组与切片
fliter1 小时前
从手写 HTTP/1.1 到拆开 HTTP/2
后端
CaffeinePro1 小时前
FastAPI自动接口文档定制与美化、权限管控
后端·fastapi
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第151题】【06_Spring篇】第11题:说一下 Spring Bean 的生命周期?
java·开发语言·后端·spring·面试
赫媒派3 小时前
Gin 12年零破坏API,架构哲学如何练成?
后端·go·gin
fliter4 小时前
Arborium:把 tree-sitter 语法高亮打包成 Rust 文档生态的基础设施
后端
张三丰24 小时前
不会写代码的高管用Claude Code两天上线新程序,工程师接手后发现:一个Bug,让AI一天烧掉一个月服务器费!
后端
Ai拆代码的曹操5 小时前
从一条转账 SQL 到分布式事务:5 种方案的全方位对比与实战
后端