大家好,我是 31 岁的小米。今天这篇文章,想给你讲一个数据库被抢光鸡蛋的大爷大妈围殴的故事。
别急,这不是段子,这是一次真实到不能再真实的缓存事故,而且几乎是 Java 社招必考题。
故事开场:超市打折,鸡蛋成了“热点 Key”
先给你一个画面。凌晨 0 点,某大型超市开始搞促销:
鸡蛋 1 块钱一斤,限量 1000 份
你是技术负责人,系统架构大概是这样:
- Redis:负责缓存商品信息
- MySQL:真实库存和商品数据
- Java 服务:对外提供查询接口
为了扛住流量,你早早就把商品信息缓存进了 Redis。
- key: product:egg:info
- value: 商品详情 + 库存信息
- TTL: 10 分钟
一切看起来都很完美。
事故发生:Key 过期的那一秒
问题就出在这 10 分钟后。发生了什么?
- product:egg:info刚好过期
- 恰好是 0 点整
- 恰好有 10 万并发请求 同时进来
于是,系统内部发生了这样一段对话:
- Java 服务 A:Redis 没数据了,我去查 DB
- Java 服务 B:Redis 没数据了,我也去查 DB
- Java 服务 C:Redis 没数据了,我也去查 DB
- ……
- MySQL:????你们要干嘛?
结果呢?
- Redis 是空的
- 所有请求同时打到 DB
- 数据库连接池被打满
- CPU 飙升
- 服务雪崩式超时
你在监控面板前,心率比双十一还高。
这道面试题,本质考的是什么?
面试官一般不会跟你讲故事,他会冷冷地问你一句:
“Redis 的热点 Key 问题你怎么解决?”
这道题,表面上问 Redis,本质上考 4 件事:
什么是「缓存热点 Key」?
我们先给它一个面试级定义:
热点 Key:在某一时间段内,被大量并发请求频繁访问的缓存 Key,一旦失效或不存在,会导致请求集中打到后端 DB。
结合我们故事里的鸡蛋:
- 商品信息访问量极高
- 所有请求都读同一个 Key
- Key 一旦过期,DB 就成了“公共厕所”
为什么热点 Key 会压垮 DB?
- 正常流程(理想情况): 请求 → Redis 命中 → 返回
- 热点 Key 失效时的真实流程: 请求 → Redis miss → 查 DB → 写 Redis → 返回
并发下的问题
当 N 个请求同时 Redis miss:
- N 次 DB 查询
- N 次写缓存
这在并发世界里,有个名字:Cache Stampede(缓存击穿)
对缓存查询加锁(核心方案)
这是你在题目里已经给出的方案,也是最经典、最常考、最容易落地的方案。
思想一句话总结:只允许一个请求去查 DB,其它请求等着
就像超市里:
- 只允许一个人去仓库拿鸡蛋
- 其他人排队
- 拿回来后,大家一起分
实现思路拆解(面试必会)
伪代码流程
Java + Redis 实战代码示例
使用 Redis SETNX 实现分布式锁
为什么一定要 Double Check?
这是面试官特别爱追问的一点。如果不 Double Check 会怎样?
场景:
- 线程 A 拿到锁
- 查 DB,写缓存
- 线程 B 刚好等待结束
- 线程 B 不检查缓存,又去查 DB
于是:
- DB 又被打了一次
- 锁白加了
Double Check 的本质:防止锁内重复查 DB
锁方案的优缺点分析(表格必考)
如果面试官继续追问怎么办?
他可能会问:
“如果锁竞争非常激烈怎么办?”
你可以这样答(加分版):
1、锁 + 本地缓存
- 锁内查 DB
- 写 Redis
- 同时写 JVM 本地缓存(Caffeine)
2、永不过期 + 异步更新
- Key 永不过期
- 后台定时异步刷新
3、提前预热热点 Key
- 活动开始前
- 主动写缓存
- 避免第一次 miss
用一句话在面试中收尾(建议背下来)
“热点 Key 的核心问题不是 Redis,而是并发请求在缓存失效瞬间对 DB 的冲击,解决思路是通过互斥控制,让只有一个请求回源 DB,其它请求等待缓存构建完成。”
说完这句话,面试官一般会点头。
END
很多同学问我:“这些场景真的会发生吗?”
我想说一句很现实的话:只要你做过促销、秒杀、榜单、首页推荐,热点 Key 一定会发生。
缓存从来不是加了就完事,缓存失效的那一秒,才是真正考验系统设计的时候。
好朋友们,我们下篇见。
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!