#jitoa #当redis“缓存“遇上“击穿“ 原创

wx644a0fff4bbcd
发布于 2023-11-12 21:11
浏览
0收藏

本博客由 金陵科技科技学院-开放原子开源社 贾子歆编写

@TOC


#jitoa #当redis“缓存“遇上“击穿“-鸿蒙开发者社区

本文原载于我的个人博客zixin.com

前言

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

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


一、什么是缓存击穿?

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


二、解决方案

有两种常见的解决方案:

  • 互斥锁:重视数据一致性,在后续学习分布式中能更深有体会。

#jitoa #当redis“缓存“遇上“击穿“-鸿蒙开发者社区

  • 逻辑过期:(从前有个火之国的木叶隐村有个叫"火影"的大牛,懒得做的事会让一众手下的忍者去处理,多好啊!)
    #jitoa #当redis“缓存“遇上“击穿“-鸿蒙开发者社区

三、实现

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

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> 

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

@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 次请求只查了一次数据库,锁生效了!
#jitoa #当redis“缓存“遇上“击穿“-鸿蒙开发者社区
#jitoa #当redis“缓存“遇上“击穿“-鸿蒙开发者社区
查看redis缓存确实存进去了,
#jitoa #当redis“缓存“遇上“击穿“-鸿蒙开发者社区
#jitoa #当redis“缓存“遇上“击穿“-鸿蒙开发者社区
由于互斥锁适合处理高并发的场景,逻辑过期适合处理逻辑较为复杂的业务场景,等找到了一个合适的复杂业务再来介绍o(╥﹏╥)o


©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
1
收藏
回复
举报
回复
    相关推荐