当前位置: 首页 > news >正文

19.redis之缓存击穿

缓存击穿

1.什么是缓存击穿??

缓存击穿,是指一个key "异常火爆"的热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
🧠 记忆钩子(批注):就像防弹衣上破了一个洞,子弹集火穿过这个洞打中数据库。

2.怎样解决?

  • 穿透后线程访问数据库前加一个锁,称为分布式锁

    public Product getProduct(String id) {// === 第一重检查 ===// 刚进来,先看看缓存有没有String value = redis.get(id);if (value != null) {return value;}// 缓存没有,准备抢锁lock.lock();try {System.out.println("我是第一个拿到锁的人,我去查库。");Product p = db.query(id);// 写回 Redisredis.set(id, p);return p;} finally {lock.unlock();}
    }
    

    但是有一种情况:多个线程同时走到访问数据库这一步的话,只有一个线程能够访问,其他线程阻塞,那么当其他线程醒来的话,不也还是会进入访问数据库逻辑吗?

  • 双重检查锁 (Double Check Lock, DCL)

    public Product getProduct(String id) {
    // === 第一重检查 ===
    // 刚进来,先看看缓存有没有
    String value = redis.get(id);
    if (value != null) {return value;
    }// 缓存没有,准备抢锁
    lock.lock();
    try {// === 【重点】第二重检查 (Double Check) ===// 拿到锁之后,必须!必须!再查一次 Redis!// 为什么?因为可能在我排队的时候,前一个人已经把数据填进去了。value = redis.get(id); if (value != null) {System.out.println("好险!前一个人已经查回来了,我直接用,不用去数据库了。");return value;}// === 只有通过了两次检查,才真的去查数据库 ===System.out.println("我是第一个拿到锁的人,我去查库。");Product p = db.query(id);// 写回 Redisredis.set(id, p);return p;} finally {lock.unlock();
    }
    }
    

3.其他锁的缺点

  • 最直觉的想法,就是用 Redis 的 SETNX (Set if Not Exists) 命令。谁设置成功,谁就拿到锁。

    //这里的锁并不是并不是我们需要访问的数据,而是固定的锁,在缓存未命中时便会出发这把锁
    //String userId = 1;未命中,则进入访问数据库阶段,即下述代码// 1. 抢锁
    if (redis.setnx("lock", "1") == 1) {// 2. 抢到了,干活doSomething();  //例如,redis.setnx("userId","ylf",3600),这里是从数据库获取的值// 3. 释放锁redis.del("lock");
    }
    

    ❌ 致命 Bug: 如果代码执行到 doSomething() 的时候,服务器突然断电了,或者程序抛出异常崩了,会发生什么? redis.del("lock") 永远不会被执行! 这就叫死锁。这个 Key 会永远留在 Redis 里,以后任何人都抢不到锁,系统直接瘫痪。
    ✅ 修正方案: 必须给锁加一个“自动过期时间”。哪怕服务器炸了,过 10 秒锁也会自动消失。 (注意:要用原子命令 SET ... NX EX ...,不能分成两行写,否则两行中间断电了也是死锁)

    // 正确:设置锁的同时,指定 10秒 后自动过期
    redis.set("lock", "1", "NX", "EX", 10);
    
  • 解决“误删” (The Wrong Delete)

    现在我们加了过期时间(比如 10 秒)。但新的问题来了。

    ❌ 致命 Bug: 假如线程 A 的业务太复杂,卡顿了 15 秒才做完。
    1. T=0s:A 拿到锁(有效期 10s)。
    2. T=10s:A 还在干活,但锁自动过期了!
    3. T=11s:线程 B 来了,一看没锁,它顺理成章拿到了锁。
    4. T=15s:A 终于干完活了,它执行 redis.del("lock")。重点来了:A 删的是谁的锁?是 B 的!
    5. T=16s:线程 C 来了,一看没锁(被 A 误删了),也拿到了锁。
    • 结果:B 和 C 同时在干活,锁完全失效了。

    ✅ 修正方案: 解铃还须系铃人。谁加的锁,只能由谁来删。 我们在 value 里不存 "1" 了,存一个唯一的 UUID。

      String myUUID = UUID.randomUUID().toString();// 加锁时,把自己的签名 (UUID) 存进去redis.set("lock", myUUID, "NX", "EX", 10);// ... 干活 ...// 释放时,先检查一下:这是不是我签名的锁?if (myUUID.equals(redis.get("lock"))) {redis.del("lock"); // 是我的,才删}
    
  • 解决“原子性” (Atomicity)

    看起来刚才的代码很完美了?不,还有一个极其隐蔽的 Bug。

    ❌ 致命 Bug: 看最后释放锁的那两行代码:
    1. if (myUUID.equals(redis.get("lock"))) <-- 这一步判断通过了。
    2. (就在这毫秒之间,JVM 发生了一次垃圾回收 GC,程序停顿了)
    3. (或者锁刚好在这时候过期了,线程 B 抢到了新锁)
    4. redis.del("lock") <-- A 醒过来,执行删除。完蛋,又把 B 的新锁删掉了!
    因为“判断”和“删除”是两个动作,不是原子的。
    

    ✅ 修正方案: 必须把这两步合并成一步。Redis 自身没有这种命令,所以我们需要用 Lua 脚本(Redis 会把 Lua 脚本作为一个整体执行,中间绝不会被打断)。

    -- 这段脚本是原子的
    if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])
    elsereturn 0
    end
    
http://www.gsyq.cn/news/89022.html

相关文章:

  • 一个由错误的拷贝构造方式产生的bug
  • 极市平台 | NeurlPS‘25开源 | 中科院新作AutoSeg3D:在线分割一切3D物体,超越ESAM!
  • 2025安全婴儿面霜测评:华西珐玛领衔,敏宝护理指南 - 资讯焦点
  • 搜维尔科技:Xsens独立项目-面向独立工作室的高端动作捕捉
  • 毕业设计实战:基于SSM+MySQL的药店管理系统设计与实现,从需求到测试轻松通关!
  • 深夜炸场!GPT-5.2发布;Meta被曝用阿里千问优化新模型;马斯克点赞腾讯游戏业务:他们的品味非常好 | 极客头条
  • Python 面向对象核心概念梳理
  • 某游戏大厂的常用面试问题解析:Netty 与 NIO - 指南
  • 【RCE】利用 Python 沙箱绕过实现任意代码执行的完整案例分析
  • 可信数据空间落地生活:医疗提速、出行省心,这些变化你已受益
  • [JSK]动态数列I
  • springboot基于vue的护士资格在线练习和模拟考试系统的设计与实现_m23x6tm9
  • springboot基于vue的档案室管理系统_gmr7teee
  • 深入解析:STM32 几种烧录方式
  • 基于Web的低代码系统的研究与实现中期检查
  • Airflow - AirflowSkipException
  • 如何快速实现离线人脸识别:FaceAISDK完整指南
  • Nextcloud文件压缩下载实用指南:轻松管理云端文件
  • 内网渗透之横向移动持久化远程控制篇——利用ipc、sc、schtasks、AT,远程连接的winrm,wmic的使用和定时任务的创建
  • 基于WEB的多媒体素材管理库的开发与应用任务书
  • 基于web的二手书交易平台设计与实开题报告
  • 爬取某网站的小说名(pyquery)
  • Android高斯模糊终极指南:Blurry库完全解析
  • 计算机毕业设计springboot基于Java的游乐园管理系统设计与实现 基于Spring Boot框架的Java游乐园综合管理系统开发与应用 Java技术驱动的Spring Boot游乐园运营管理系
  • 基于web的二手书交易平台设计与实现
  • RAD Studio 13 Florence:C++、Delphi现代化与AI驱动的跨平台开发新范式
  • GBase 8a数据库多实例部署流程简介
  • YashanDB数据库的多维扩展能力与性能提升路径
  • COMSOL模拟:单场耦合下的注二氧化碳驱替甲烷模型研究
  • GBase数据库护航国家管网SCADA系统四年无中断平稳运行