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

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

相关推荐
bobz965几秒前
ovn db schema 22.12 vs 24.03
后端
卓伊凡2 分钟前
复杂项目即时通讯从android 5升级android x后遗症之解决报错 #10 java.lang.NullPointerException-优雅草卓伊凡|
前端·后端
dl7434 分钟前
一文看懂spring扫描bean的过程
后端
柑木7 分钟前
密码学-基础理论-DiffieHellman密钥交换
后端·安全
_風箏9 分钟前
Zabbix【部署 05】 Docker部署Zabbix Server Agent Agent2 Web interface及 Java-Gate-Way
后端
平行绳9 分钟前
免费证书有效期越来越短了!本文教你设置自动更新证书,超详细!
后端·https
天行健的回响16 分钟前
ThreadLocal自学小记
后端
现在就干19 分钟前
从卡死到秒出:百万级数据导出优化实战
后端
一乐小哥20 分钟前
从 JDK 到 Spring,单例模式在源码中的实战用法
后端·设计模式
nppe621 分钟前
sequlize操作mysql小记
前端·后端