Redis 从入门到精通(七):缓存设计与最佳实践 ------ 穿透、击穿、雪崩与一致性终极指南
一、缓存架构的黄金法则
1.1 缓存在系统中的位置
#mermaid-svg-u3HCtaPhfAugizzN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-u3HCtaPhfAugizzN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-u3HCtaPhfAugizzN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-u3HCtaPhfAugizzN .error-icon{fill:#552222;}#mermaid-svg-u3HCtaPhfAugizzN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-u3HCtaPhfAugizzN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-u3HCtaPhfAugizzN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-u3HCtaPhfAugizzN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-u3HCtaPhfAugizzN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-u3HCtaPhfAugizzN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-u3HCtaPhfAugizzN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-u3HCtaPhfAugizzN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-u3HCtaPhfAugizzN .marker.cross{stroke:#333333;}#mermaid-svg-u3HCtaPhfAugizzN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-u3HCtaPhfAugizzN p{margin:0;}#mermaid-svg-u3HCtaPhfAugizzN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-u3HCtaPhfAugizzN .cluster-label text{fill:#333;}#mermaid-svg-u3HCtaPhfAugizzN .cluster-label span{color:#333;}#mermaid-svg-u3HCtaPhfAugizzN .cluster-label span p{background-color:transparent;}#mermaid-svg-u3HCtaPhfAugizzN .label text,#mermaid-svg-u3HCtaPhfAugizzN span{fill:#333;color:#333;}#mermaid-svg-u3HCtaPhfAugizzN .node rect,#mermaid-svg-u3HCtaPhfAugizzN .node circle,#mermaid-svg-u3HCtaPhfAugizzN .node ellipse,#mermaid-svg-u3HCtaPhfAugizzN .node polygon,#mermaid-svg-u3HCtaPhfAugizzN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-u3HCtaPhfAugizzN .rough-node .label text,#mermaid-svg-u3HCtaPhfAugizzN .node .label text,#mermaid-svg-u3HCtaPhfAugizzN .image-shape .label,#mermaid-svg-u3HCtaPhfAugizzN .icon-shape .label{text-anchor:middle;}#mermaid-svg-u3HCtaPhfAugizzN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-u3HCtaPhfAugizzN .rough-node .label,#mermaid-svg-u3HCtaPhfAugizzN .node .label,#mermaid-svg-u3HCtaPhfAugizzN .image-shape .label,#mermaid-svg-u3HCtaPhfAugizzN .icon-shape .label{text-align:center;}#mermaid-svg-u3HCtaPhfAugizzN .node.clickable{cursor:pointer;}#mermaid-svg-u3HCtaPhfAugizzN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-u3HCtaPhfAugizzN .arrowheadPath{fill:#333333;}#mermaid-svg-u3HCtaPhfAugizzN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-u3HCtaPhfAugizzN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-u3HCtaPhfAugizzN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u3HCtaPhfAugizzN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-u3HCtaPhfAugizzN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u3HCtaPhfAugizzN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-u3HCtaPhfAugizzN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-u3HCtaPhfAugizzN .cluster text{fill:#333;}#mermaid-svg-u3HCtaPhfAugizzN .cluster span{color:#333;}#mermaid-svg-u3HCtaPhfAugizzN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-u3HCtaPhfAugizzN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-u3HCtaPhfAugizzN rect.text{fill:none;stroke-width:0;}#mermaid-svg-u3HCtaPhfAugizzN .icon-shape,#mermaid-svg-u3HCtaPhfAugizzN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u3HCtaPhfAugizzN .icon-shape p,#mermaid-svg-u3HCtaPhfAugizzN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-u3HCtaPhfAugizzN .icon-shape .label rect,#mermaid-svg-u3HCtaPhfAugizzN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u3HCtaPhfAugizzN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-u3HCtaPhfAugizzN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-u3HCtaPhfAugizzN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Cache Hit
直接返回
Cache Miss
查数据库
回写缓存
客户端
应用服务
Redis 缓存层
< 1ms 响应
数据库
~10ms 响应
缓存的本质是用空间换时间 ,把高频访问的热点数据放在离 CPU 更近、速度更快的地方。但这带来了一个根本性问题:缓存和数据库有两份数据,如何保证一致性?
1.2 缓存设计的三个核心维度
缓存设计
┌───┼───┐
命中率 一致性 容灾
(多快) (多准) (多稳)
在正式讨论问题之前,先记住一个原则:缓存不是银弹,如果你能接受直接查数据库的延迟,就不要加缓存。 每加一层缓存,就多一个可能出错的环节。
二、缓存三大经典问题:穿透、击穿、雪崩
这三个问题是面试和实际生产中最常遇到的缓存故障,但很多人分不清它们的区别。我用一张图讲清楚:
#mermaid-svg-mJ3zdM7bskb2cTvu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mJ3zdM7bskb2cTvu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mJ3zdM7bskb2cTvu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mJ3zdM7bskb2cTvu .error-icon{fill:#552222;}#mermaid-svg-mJ3zdM7bskb2cTvu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mJ3zdM7bskb2cTvu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mJ3zdM7bskb2cTvu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mJ3zdM7bskb2cTvu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mJ3zdM7bskb2cTvu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mJ3zdM7bskb2cTvu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mJ3zdM7bskb2cTvu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mJ3zdM7bskb2cTvu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mJ3zdM7bskb2cTvu .marker.cross{stroke:#333333;}#mermaid-svg-mJ3zdM7bskb2cTvu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mJ3zdM7bskb2cTvu p{margin:0;}#mermaid-svg-mJ3zdM7bskb2cTvu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mJ3zdM7bskb2cTvu .cluster-label text{fill:#333;}#mermaid-svg-mJ3zdM7bskb2cTvu .cluster-label span{color:#333;}#mermaid-svg-mJ3zdM7bskb2cTvu .cluster-label span p{background-color:transparent;}#mermaid-svg-mJ3zdM7bskb2cTvu .label text,#mermaid-svg-mJ3zdM7bskb2cTvu span{fill:#333;color:#333;}#mermaid-svg-mJ3zdM7bskb2cTvu .node rect,#mermaid-svg-mJ3zdM7bskb2cTvu .node circle,#mermaid-svg-mJ3zdM7bskb2cTvu .node ellipse,#mermaid-svg-mJ3zdM7bskb2cTvu .node polygon,#mermaid-svg-mJ3zdM7bskb2cTvu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mJ3zdM7bskb2cTvu .rough-node .label text,#mermaid-svg-mJ3zdM7bskb2cTvu .node .label text,#mermaid-svg-mJ3zdM7bskb2cTvu .image-shape .label,#mermaid-svg-mJ3zdM7bskb2cTvu .icon-shape .label{text-anchor:middle;}#mermaid-svg-mJ3zdM7bskb2cTvu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mJ3zdM7bskb2cTvu .rough-node .label,#mermaid-svg-mJ3zdM7bskb2cTvu .node .label,#mermaid-svg-mJ3zdM7bskb2cTvu .image-shape .label,#mermaid-svg-mJ3zdM7bskb2cTvu .icon-shape .label{text-align:center;}#mermaid-svg-mJ3zdM7bskb2cTvu .node.clickable{cursor:pointer;}#mermaid-svg-mJ3zdM7bskb2cTvu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mJ3zdM7bskb2cTvu .arrowheadPath{fill:#333333;}#mermaid-svg-mJ3zdM7bskb2cTvu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mJ3zdM7bskb2cTvu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mJ3zdM7bskb2cTvu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mJ3zdM7bskb2cTvu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mJ3zdM7bskb2cTvu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mJ3zdM7bskb2cTvu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mJ3zdM7bskb2cTvu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mJ3zdM7bskb2cTvu .cluster text{fill:#333;}#mermaid-svg-mJ3zdM7bskb2cTvu .cluster span{color:#333;}#mermaid-svg-mJ3zdM7bskb2cTvu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mJ3zdM7bskb2cTvu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mJ3zdM7bskb2cTvu rect.text{fill:none;stroke-width:0;}#mermaid-svg-mJ3zdM7bskb2cTvu .icon-shape,#mermaid-svg-mJ3zdM7bskb2cTvu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mJ3zdM7bskb2cTvu .icon-shape p,#mermaid-svg-mJ3zdM7bskb2cTvu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mJ3zdM7bskb2cTvu .icon-shape .label rect,#mermaid-svg-mJ3zdM7bskb2cTvu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mJ3zdM7bskb2cTvu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mJ3zdM7bskb2cTvu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mJ3zdM7bskb2cTvu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 缓存击穿
某个热点 key 过期
大量并发请求
同时去查 DB 同一个 key
数据库瞬时压力暴增
缓存穿透
查询不存在的数据
(如 id=-1)
缓存永远没有
每次请求都打到 DB
恶意攻击/大量无效请求
缓存雪崩
大量 key 同时过期
或 Redis 宕机
请求全部打到数据库
数据库被打挂
2.1 缓存穿透(Cache Penetration)
定义 :查询一个数据库中也不存在的数据,缓存永远不会有,每次请求都穿过缓存直达数据库。
典型场景:
- 恶意攻击:用不存在的 id(负数、超大数)疯狂请求接口
- 业务漏洞:商品已下架但 id 仍被爬虫遍历
- 用户输入非法参数
方案一:布隆过滤器(Bloom Filter)
布隆过滤器是一种概率型数据结构 ,可以告诉你"这个 key 一定不存在 "或"这个 key 可能存在"。
#mermaid-svg-qHf16HIQxNjSK0sL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qHf16HIQxNjSK0sL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qHf16HIQxNjSK0sL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qHf16HIQxNjSK0sL .error-icon{fill:#552222;}#mermaid-svg-qHf16HIQxNjSK0sL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qHf16HIQxNjSK0sL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qHf16HIQxNjSK0sL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qHf16HIQxNjSK0sL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qHf16HIQxNjSK0sL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qHf16HIQxNjSK0sL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qHf16HIQxNjSK0sL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qHf16HIQxNjSK0sL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qHf16HIQxNjSK0sL .marker.cross{stroke:#333333;}#mermaid-svg-qHf16HIQxNjSK0sL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qHf16HIQxNjSK0sL p{margin:0;}#mermaid-svg-qHf16HIQxNjSK0sL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qHf16HIQxNjSK0sL .cluster-label text{fill:#333;}#mermaid-svg-qHf16HIQxNjSK0sL .cluster-label span{color:#333;}#mermaid-svg-qHf16HIQxNjSK0sL .cluster-label span p{background-color:transparent;}#mermaid-svg-qHf16HIQxNjSK0sL .label text,#mermaid-svg-qHf16HIQxNjSK0sL span{fill:#333;color:#333;}#mermaid-svg-qHf16HIQxNjSK0sL .node rect,#mermaid-svg-qHf16HIQxNjSK0sL .node circle,#mermaid-svg-qHf16HIQxNjSK0sL .node ellipse,#mermaid-svg-qHf16HIQxNjSK0sL .node polygon,#mermaid-svg-qHf16HIQxNjSK0sL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qHf16HIQxNjSK0sL .rough-node .label text,#mermaid-svg-qHf16HIQxNjSK0sL .node .label text,#mermaid-svg-qHf16HIQxNjSK0sL .image-shape .label,#mermaid-svg-qHf16HIQxNjSK0sL .icon-shape .label{text-anchor:middle;}#mermaid-svg-qHf16HIQxNjSK0sL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qHf16HIQxNjSK0sL .rough-node .label,#mermaid-svg-qHf16HIQxNjSK0sL .node .label,#mermaid-svg-qHf16HIQxNjSK0sL .image-shape .label,#mermaid-svg-qHf16HIQxNjSK0sL .icon-shape .label{text-align:center;}#mermaid-svg-qHf16HIQxNjSK0sL .node.clickable{cursor:pointer;}#mermaid-svg-qHf16HIQxNjSK0sL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qHf16HIQxNjSK0sL .arrowheadPath{fill:#333333;}#mermaid-svg-qHf16HIQxNjSK0sL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qHf16HIQxNjSK0sL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qHf16HIQxNjSK0sL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qHf16HIQxNjSK0sL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qHf16HIQxNjSK0sL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qHf16HIQxNjSK0sL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qHf16HIQxNjSK0sL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qHf16HIQxNjSK0sL .cluster text{fill:#333;}#mermaid-svg-qHf16HIQxNjSK0sL .cluster span{color:#333;}#mermaid-svg-qHf16HIQxNjSK0sL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qHf16HIQxNjSK0sL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qHf16HIQxNjSK0sL rect.text{fill:none;stroke-width:0;}#mermaid-svg-qHf16HIQxNjSK0sL .icon-shape,#mermaid-svg-qHf16HIQxNjSK0sL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qHf16HIQxNjSK0sL .icon-shape p,#mermaid-svg-qHf16HIQxNjSK0sL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qHf16HIQxNjSK0sL .icon-shape .label rect,#mermaid-svg-qHf16HIQxNjSK0sL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qHf16HIQxNjSK0sL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qHf16HIQxNjSK0sL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qHf16HIQxNjSK0sL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 一定不存在
可能存在
(有误判率)
Cache Miss
请求 key
布隆过滤器
直接拒绝
不查缓存,不查 DB
查缓存
查数据库
bash
# RedisBloom 模块(需要单独安装)
# 或使用 Redisson 的布隆过滤器(纯内存实现)
# 创建布隆过滤器(预计 100 万条数据,误判率 0.01%)
BF.RESERVE bloom:product 0.001 1000000
# 初始化:把所有合法 id 加入布隆过滤器
BF.ADD bloom:product 1001
BF.ADD bloom:product 1002
BF.MADD bloom:product 1003 1004 1005
# 查询时先检查
BF.EXISTS bloom:product -1 # 返回 0(一定不存在)
BF.EXISTS bloom:product 1001 # 返回 1(可能存在)
布隆过滤器的权衡:
- 位数组越大,误判率越低,但内存占用越大
- Hash 函数越多,误判率越低,但计算成本越高
- 不能删除元素(需要计数布隆过滤器或布谷鸟过滤器)
方案二:缓存空值
最简单有效的方式------即使查不到数据,也缓存一个空值标记:
java
public Product getProduct(Long id) {
String cacheKey = "product:" + id;
Product product = redis.get(cacheKey);
if (product != null) {
return product.isNullMarker() ? null : product;
}
product = db.findById(id);
if (product == null) {
// 缓存空值,过期时间短一些
redis.set(cacheKey, NullMarker.INSTANCE, 60); // 60 秒
} else {
redis.set(cacheKey, product, 3600); // 1 小时
}
return product;
}
注意事项:空值缓存过期时间要短(1-5 分钟),否则业务数据更新后缓存仍返回空值。
方案三:参数校验
在网关层或接口层做参数合法性校验,直接拦截明显非法的请求(如 id < 0,id 格式不正确等)。
方案对比
| 方案 | 实现成本 | 内存开销 | 维护成本 | 推荐场景 |
|---|---|---|---|---|
| 布隆过滤器 | 高(需额外模块或内存) | 低(位数组) | 需要维护数据一致性 | 大量数据、高并发 |
| 缓存空值 | 低 | 极低 | 低 | 中小规模、简单场景 |
| 参数校验 | 低 | 无 | 无 | 所有场景,第一道防线 |
推荐策略 :参数校验 + 缓存空值 打底,数据量大时加布隆过滤器。
2.2 缓存击穿(Cache Breakdown)
定义 :单个热点 key 过期的瞬间,大量并发请求同时去查数据库,把数据库打挂。
典型场景:
- 首页热门商品缓存刚好过期
- 秒杀活动的商品详情页
- 微博热点话题
方案一:互斥锁(Mutex Lock)
只让一个请求去查数据库,其他请求等待:
java
public Product getProductWithLock(Long id) {
String cacheKey = "product:" + id;
String lockKey = "lock:product:" + id;
Product product = redis.get(cacheKey);
if (product != null) return product;
// 尝试获取锁(SET NX EX)
boolean locked = redis.set(lockKey, "1", "NX", "EX", 10);
if (locked) {
try {
// 双重检查:可能别的线程已经重建了缓存
product = redis.get(cacheKey);
if (product != null) return product;
// 查数据库
product = db.findById(id);
if (product != null) {
redis.set(cacheKey, product, 3600);
} else {
// 缓存空值防穿透
redis.set(cacheKey, NullMarker.INSTANCE, 60);
}
return product;
} finally {
redis.del(lockKey);
}
} else {
// 没抢到锁,休眠重试
Thread.sleep(50);
return getProductWithLock(id); // 重试
}
}
数据库 Redis 请求3(并发) 请求2(并发) 请求1 数据库 Redis 请求3(并发) 请求2(并发) 请求1 #mermaid-svg-z1FwgsrQ7ZajNxx9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z1FwgsrQ7ZajNxx9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .error-icon{fill:#552222;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .marker.cross{stroke:#333333;}#mermaid-svg-z1FwgsrQ7ZajNxx9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z1FwgsrQ7ZajNxx9 p{margin:0;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-z1FwgsrQ7ZajNxx9 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-z1FwgsrQ7ZajNxx9 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-z1FwgsrQ7ZajNxx9 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .sequenceNumber{fill:white;}#mermaid-svg-z1FwgsrQ7ZajNxx9 #sequencenumber{fill:#333;}#mermaid-svg-z1FwgsrQ7ZajNxx9 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .messageText{fill:#333;stroke:none;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .labelText,#mermaid-svg-z1FwgsrQ7ZajNxx9 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .loopText,#mermaid-svg-z1FwgsrQ7ZajNxx9 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-z1FwgsrQ7ZajNxx9 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .noteText,#mermaid-svg-z1FwgsrQ7ZajNxx9 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .actorPopupMenu{position:absolute;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-z1FwgsrQ7ZajNxx9 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-z1FwgsrQ7ZajNxx9 .actor-man circle,#mermaid-svg-z1FwgsrQ7ZajNxx9 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-z1FwgsrQ7ZajNxx9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 热点 key 过期 查缓存 → Miss查缓存 → Miss查缓存 → MissSET lock NX → OKSET lock NX → FAIL(锁已被抢)SET lock NX → FAIL查询数据休眠 50ms 后重试休眠 50ms 后重试返回数据写回缓存DEL lock查缓存 → Hit!查缓存 → Hit!
方案二:逻辑过期(不设 TTL,值中存过期时间)
热点数据不设 Redis 过期时间,而是在 value 中存储逻辑过期时间。后台异步更新:
java
@Data
class CacheData<T> {
private T data; // 实际数据
private long expireAt; // 逻辑过期时间戳
}
public Product getProductLogicalExpire(Long id) {
String cacheKey = "product:" + id;
CacheData<Product> cacheData = redis.get(cacheKey);
if (cacheData == null) {
// 首次加载,加锁重建
return rebuildCache(id);
}
// 检查是否逻辑过期
if (cacheData.getExpireAt() > System.currentTimeMillis()) {
return cacheData.getData(); // 未过期,直接返回
}
// 已逻辑过期:获取互斥锁,开新线程重建
String lockKey = "lock:product:" + id;
if (redis.set(lockKey, "1", "NX", "EX", 10)) {
// 异步重建(不阻塞当前请求)
executorService.submit(() -> {
try {
Product newProduct = db.findById(id);
CacheData<Product> newCacheData = new CacheData<>();
newCacheData.setData(newProduct);
newCacheData.setExpireAt(System.currentTimeMillis() + 3600_000);
redis.set(cacheKey, newCacheData);
} finally {
redis.del(lockKey);
}
});
}
// 返回旧数据(即使是过期的,也比查不到强)
return cacheData.getData();
}
方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 互斥锁 | 实现简单,数据库压力可控 | 请求需要等待,有少量延迟 | 数据强一致性要求高 |
| 逻辑过期 | 零等待,用户无感知 | 返回旧数据,实现复杂 | 高并发热点,允许短暂不一致 |
2.3 缓存雪崩(Cache Avalanche)
定义 :大量 key 同时过期 或Redis 服务宕机,导致所有请求瞬间打到数据库。
典型场景:
- 批量导入数据时所有缓存同时设置相同的过期时间
- Redis 集群整体故障
- 缓存预热不足导致大量 key 同一时段过期
方案一:过期时间加随机值
不要让所有 key 在同一秒过期:
java
// ❌ 错误做法:所有 key 过期时间完全一样
redis.set("product:" + id, product, 3600);
// ✅ 正确做法:过期时间加随机偏移
int baseExpire = 3600; // 基础 1 小时
int randomDelta = new Random().nextInt(600); // 随机 0~10 分钟
redis.set("product:" + id, product, baseExpire + randomDelta);
方案二:多级缓存 + 降级
请求 → 本地缓存(Caffeine) → Redis → 数据库
↓ ↓ ↓
命中返回 命中返回 都 miss 才查 DB
java
// 本地缓存作为 Redis 之前的最后一道防线
Cache<String, Product> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
public Product getProduct(Long id) {
String key = "product:" + id;
// L1: 本地缓存
Product product = localCache.getIfPresent(key);
if (product != null) return product;
// L2: Redis
product = redis.get(key);
if (product != null) {
localCache.put(key, product);
return product;
}
// L3: 数据库
product = db.findById(id);
if (product != null) {
redis.set(key, product, 3600 + random.nextInt(600));
localCache.put(key, product);
}
return product;
}
方案三:服务熔断与限流
当 Redis 不可用时,通过熔断器(如 Hystrix、Sentinel)快速失败或降级,避免请求堆积打垮数据库:
java
@HystrixCommand(fallbackMethod = "getProductFallback")
public Product getProduct(Long id) {
// 正常查 Redis + DB
}
public Product getProductFallback(Long id) {
// 降级逻辑:返回固定值 / 默认值 / 空值
return Product.DEFAULT;
}
雪崩方案总结
| 防线 | 方案 | 作用 |
|---|---|---|
| 第一道 | 过期时间加随机值 | 避免集中过期 |
| 第二道 | 多级缓存 | Redis 挂了还有本地缓存兜底 |
| 第三道 | 熔断降级 | 防止连锁故障 |
| 第四道 | Redis 高可用 | Sentinel / Cluster,减少 Redis 宕机概率 |
三、缓存一致性:如何保证缓存和数据库的数据一致?
这是缓存设计中最难的问题------强一致性 和高性能天生矛盾。
3.1 三种缓存读写策略
#mermaid-svg-J5b4SXA5fcBNIjAL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-J5b4SXA5fcBNIjAL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-J5b4SXA5fcBNIjAL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-J5b4SXA5fcBNIjAL .error-icon{fill:#552222;}#mermaid-svg-J5b4SXA5fcBNIjAL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-J5b4SXA5fcBNIjAL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-J5b4SXA5fcBNIjAL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-J5b4SXA5fcBNIjAL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-J5b4SXA5fcBNIjAL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-J5b4SXA5fcBNIjAL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-J5b4SXA5fcBNIjAL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-J5b4SXA5fcBNIjAL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-J5b4SXA5fcBNIjAL .marker.cross{stroke:#333333;}#mermaid-svg-J5b4SXA5fcBNIjAL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-J5b4SXA5fcBNIjAL p{margin:0;}#mermaid-svg-J5b4SXA5fcBNIjAL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-J5b4SXA5fcBNIjAL .cluster-label text{fill:#333;}#mermaid-svg-J5b4SXA5fcBNIjAL .cluster-label span{color:#333;}#mermaid-svg-J5b4SXA5fcBNIjAL .cluster-label span p{background-color:transparent;}#mermaid-svg-J5b4SXA5fcBNIjAL .label text,#mermaid-svg-J5b4SXA5fcBNIjAL span{fill:#333;color:#333;}#mermaid-svg-J5b4SXA5fcBNIjAL .node rect,#mermaid-svg-J5b4SXA5fcBNIjAL .node circle,#mermaid-svg-J5b4SXA5fcBNIjAL .node ellipse,#mermaid-svg-J5b4SXA5fcBNIjAL .node polygon,#mermaid-svg-J5b4SXA5fcBNIjAL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-J5b4SXA5fcBNIjAL .rough-node .label text,#mermaid-svg-J5b4SXA5fcBNIjAL .node .label text,#mermaid-svg-J5b4SXA5fcBNIjAL .image-shape .label,#mermaid-svg-J5b4SXA5fcBNIjAL .icon-shape .label{text-anchor:middle;}#mermaid-svg-J5b4SXA5fcBNIjAL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-J5b4SXA5fcBNIjAL .rough-node .label,#mermaid-svg-J5b4SXA5fcBNIjAL .node .label,#mermaid-svg-J5b4SXA5fcBNIjAL .image-shape .label,#mermaid-svg-J5b4SXA5fcBNIjAL .icon-shape .label{text-align:center;}#mermaid-svg-J5b4SXA5fcBNIjAL .node.clickable{cursor:pointer;}#mermaid-svg-J5b4SXA5fcBNIjAL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-J5b4SXA5fcBNIjAL .arrowheadPath{fill:#333333;}#mermaid-svg-J5b4SXA5fcBNIjAL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-J5b4SXA5fcBNIjAL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-J5b4SXA5fcBNIjAL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J5b4SXA5fcBNIjAL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-J5b4SXA5fcBNIjAL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J5b4SXA5fcBNIjAL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-J5b4SXA5fcBNIjAL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-J5b4SXA5fcBNIjAL .cluster text{fill:#333;}#mermaid-svg-J5b4SXA5fcBNIjAL .cluster span{color:#333;}#mermaid-svg-J5b4SXA5fcBNIjAL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-J5b4SXA5fcBNIjAL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-J5b4SXA5fcBNIjAL rect.text{fill:none;stroke-width:0;}#mermaid-svg-J5b4SXA5fcBNIjAL .icon-shape,#mermaid-svg-J5b4SXA5fcBNIjAL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J5b4SXA5fcBNIjAL .icon-shape p,#mermaid-svg-J5b4SXA5fcBNIjAL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-J5b4SXA5fcBNIjAL .icon-shape .label rect,#mermaid-svg-J5b4SXA5fcBNIjAL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J5b4SXA5fcBNIjAL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-J5b4SXA5fcBNIjAL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-J5b4SXA5fcBNIjAL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Write-Behind(异步回写)
写:应用 → 缓存 → 异步批量写 DB
DB 写入延迟,性能极高
一致性最弱,可能丢数据
Read/Write-Through(读写穿透)
读:应用 → 缓存层 → 自动查 DB
写:应用 → 缓存层 → 同步写 DB
缓存层封装了 DB 操作
Cache-Aside(旁路缓存)
读:先查缓存 → Miss 则查 DB → 写缓存
写:更新 DB → 删除缓存
应用直接控制缓存
| 策略 | 读流程 | 写流程 | 一致性 | 性能 | 适用场景 |
|---|---|---|---|---|---|
| Cache-Aside | 应用自己控制缓存 | 应用更新 DB 后删缓存 | 中等(最终一致) | 高 | 90% 的业务场景 |
| Read/Write-Through | 缓存层代理 | 缓存层同步写 DB | 较高 | 中 | 缓存与 DB 紧耦合 |
| Write-Behind | 缓存层代理 | 异步批量写 DB | 低 | 极高 | 写密集、可容忍少量丢失 |
3.2 Cache-Aside 的经典问题:先删缓存还是先更新数据库?
这是面试必考题。 假设更新一个商品的库存:
错误做法:先删缓存,再更新数据库
数据库 缓存 线程B(读) 线程A(写) 数据库 缓存 线程B(读) 线程A(写) #mermaid-svg-BA4DBTSQwXSK2Wnk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BA4DBTSQwXSK2Wnk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BA4DBTSQwXSK2Wnk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BA4DBTSQwXSK2Wnk .error-icon{fill:#552222;}#mermaid-svg-BA4DBTSQwXSK2Wnk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BA4DBTSQwXSK2Wnk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BA4DBTSQwXSK2Wnk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BA4DBTSQwXSK2Wnk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BA4DBTSQwXSK2Wnk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BA4DBTSQwXSK2Wnk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BA4DBTSQwXSK2Wnk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BA4DBTSQwXSK2Wnk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BA4DBTSQwXSK2Wnk .marker.cross{stroke:#333333;}#mermaid-svg-BA4DBTSQwXSK2Wnk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BA4DBTSQwXSK2Wnk p{margin:0;}#mermaid-svg-BA4DBTSQwXSK2Wnk .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BA4DBTSQwXSK2Wnk text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-BA4DBTSQwXSK2Wnk .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BA4DBTSQwXSK2Wnk .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-BA4DBTSQwXSK2Wnk .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-BA4DBTSQwXSK2Wnk .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-BA4DBTSQwXSK2Wnk #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-BA4DBTSQwXSK2Wnk .sequenceNumber{fill:white;}#mermaid-svg-BA4DBTSQwXSK2Wnk #sequencenumber{fill:#333;}#mermaid-svg-BA4DBTSQwXSK2Wnk #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-BA4DBTSQwXSK2Wnk .messageText{fill:#333;stroke:none;}#mermaid-svg-BA4DBTSQwXSK2Wnk .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BA4DBTSQwXSK2Wnk .labelText,#mermaid-svg-BA4DBTSQwXSK2Wnk .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-BA4DBTSQwXSK2Wnk .loopText,#mermaid-svg-BA4DBTSQwXSK2Wnk .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-BA4DBTSQwXSK2Wnk .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BA4DBTSQwXSK2Wnk .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-BA4DBTSQwXSK2Wnk .noteText,#mermaid-svg-BA4DBTSQwXSK2Wnk .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-BA4DBTSQwXSK2Wnk .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BA4DBTSQwXSK2Wnk .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BA4DBTSQwXSK2Wnk .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BA4DBTSQwXSK2Wnk .actorPopupMenu{position:absolute;}#mermaid-svg-BA4DBTSQwXSK2Wnk .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-BA4DBTSQwXSK2Wnk .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BA4DBTSQwXSK2Wnk .actor-man circle,#mermaid-svg-BA4DBTSQwXSK2Wnk line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-BA4DBTSQwXSK2Wnk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 缓存中是旧值数据库是新值数据不一致! 1. 删除缓存2. 读缓存 → Miss3. 查数据库(读到旧值)4. 把旧值写入缓存5. 更新数据库为新值
正确做法:先更新数据库,再删除缓存
数据库 缓存 线程B(读) 线程A(写) 数据库 缓存 线程B(读) 线程A(写) #mermaid-svg-0BoJTn5dKwqNwCN4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0BoJTn5dKwqNwCN4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0BoJTn5dKwqNwCN4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0BoJTn5dKwqNwCN4 .error-icon{fill:#552222;}#mermaid-svg-0BoJTn5dKwqNwCN4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0BoJTn5dKwqNwCN4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0BoJTn5dKwqNwCN4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0BoJTn5dKwqNwCN4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0BoJTn5dKwqNwCN4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0BoJTn5dKwqNwCN4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0BoJTn5dKwqNwCN4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0BoJTn5dKwqNwCN4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0BoJTn5dKwqNwCN4 .marker.cross{stroke:#333333;}#mermaid-svg-0BoJTn5dKwqNwCN4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0BoJTn5dKwqNwCN4 p{margin:0;}#mermaid-svg-0BoJTn5dKwqNwCN4 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0BoJTn5dKwqNwCN4 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-0BoJTn5dKwqNwCN4 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0BoJTn5dKwqNwCN4 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-0BoJTn5dKwqNwCN4 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-0BoJTn5dKwqNwCN4 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-0BoJTn5dKwqNwCN4 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-0BoJTn5dKwqNwCN4 .sequenceNumber{fill:white;}#mermaid-svg-0BoJTn5dKwqNwCN4 #sequencenumber{fill:#333;}#mermaid-svg-0BoJTn5dKwqNwCN4 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-0BoJTn5dKwqNwCN4 .messageText{fill:#333;stroke:none;}#mermaid-svg-0BoJTn5dKwqNwCN4 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0BoJTn5dKwqNwCN4 .labelText,#mermaid-svg-0BoJTn5dKwqNwCN4 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-0BoJTn5dKwqNwCN4 .loopText,#mermaid-svg-0BoJTn5dKwqNwCN4 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-0BoJTn5dKwqNwCN4 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0BoJTn5dKwqNwCN4 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-0BoJTn5dKwqNwCN4 .noteText,#mermaid-svg-0BoJTn5dKwqNwCN4 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-0BoJTn5dKwqNwCN4 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0BoJTn5dKwqNwCN4 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0BoJTn5dKwqNwCN4 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0BoJTn5dKwqNwCN4 .actorPopupMenu{position:absolute;}#mermaid-svg-0BoJTn5dKwqNwCN4 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-0BoJTn5dKwqNwCN4 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0BoJTn5dKwqNwCN4 .actor-man circle,#mermaid-svg-0BoJTn5dKwqNwCN4 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-0BoJTn5dKwqNwCN4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 理论上第5步可能发生在第4步之前但概率极低(读DB比写操作快)且可以通过"延迟双删"解决 1. 读缓存 → Miss2. 查数据库(旧值)3. 更新数据库(新值)4. 删除缓存5. 写入缓存(旧值)← 这步发生在删缓存之前
延迟双删(Double Delete)来兜底:
java
public void updateProduct(Product product) {
// 第一次删缓存
redis.del("product:" + product.getId());
// 更新数据库
db.update(product);
// 休眠一段时间后第二次删缓存(异步)
// 休眠时间 ≈ 读 DB + 写缓存的时间
executorService.schedule(() -> {
redis.del("product:" + product.getId());
}, 500, TimeUnit.MILLISECONDS);
}
3.3 最终一致性方案:基于 Binlog 的异步更新
对于强一致性要求不高的场景,可以监听 MySQL Binlog,通过 Canal 等工具异步更新缓存:
MySQL → Canal(监听 Binlog) → MQ(Kafka/RocketMQ) → 消费者更新 Redis
这是目前大厂最主流的缓存一致性方案,完全解耦了业务代码和缓存更新逻辑。
四、缓存淘汰策略:内存满了怎么办?
4.1 Redis 支持的淘汰策略
当 Redis 内存达到 maxmemory 时,会触发淘汰。Redis 提供了 8 种策略:
| 策略 | 行为 | 适用场景 |
|---|---|---|
noeviction |
不淘汰,写入报错 | 不应使用的默认值 |
allkeys-lru |
所有 key 中淘汰最近最少使用的 | 通用推荐 |
allkeys-lfu |
所有 key 中淘汰最不经常使用的 | 有冷热数据区分 |
allkeys-random |
随机淘汰 | 访问模式均匀 |
volatile-lru |
有过期时间的 key 中淘汰 LRU | 部分永久 key + 部分临时 key |
volatile-lfu |
有过期时间的 key 中淘汰 LFU | 同上,但用访问频率 |
volatile-random |
有过期时间的 key 中随机淘汰 | 同上 |
volatile-ttl |
有过期时间的 key 中淘汰 TTL 最短的 | 优先淘汰即将过期的 |
bash
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru
4.2 LRU vs LFU
| 维度 | LRU(Least Recently Used) | LFU(Least Frequently Used) |
|---|---|---|
| 淘汰依据 | 最近访问时间 | 访问频率 |
| 优势 | 对突发流量友好 | 保留长期热点数据 |
| 劣势 | 偶然访问一次的冷数据占坑 | 历史高频数据长期占坑 |
| 实现 | Redis 用 24 位存储 LRU 时钟秒数 | Redis 用 24 位存储 LFU 计数值(高16位时间+低8位对数计数器) |
| 典型场景 | 新闻资讯(时效性强) | 电商商品(长期热门需要保留) |
Redis 的 LRU 不是严格的 LRU (严格 LRU 需要额外双向链表),而是近似 LRU :随机采样 N 个 key(maxmemory-samples 默认 5),淘汰其中最旧的。
bash
maxmemory-samples 10 # 采样数越大越接近严格 LRU,但 CPU 消耗越大
4.3 LFU 的对数计数器
Redis LFU 使用 8 位对数计数器,不是线性递增,而是概率性递增:
计数器增长概率 = 1 / (counter * lfu_log_factor + 1)
counter=1 → 增长概率 ≈ 100%
counter=10 → 增长概率 ≈ 10%
counter=100→ 增长概率 ≈ 1%
counter=255→ 基本不再增长
同时 LFU 有衰减机制 :lfu_decay_time(默认 1 分钟),每分钟计数器减 1。长期不访问的 key 最终被淘汰。
bash
lfu-log-factor 10 # 越大,高 count 越难增长(默认 10)
lfu-decay-time 1 # 衰减周期(分钟,默认 1)
五、大 Key 与热 Key:缓存性能的两大杀手
5.1 大 Key 问题
定义:单个 key 的 value 过大。通常认为 String 类型 > 10KB 或集合类型 > 10000 个元素即为大 Key。
危害:
| 影响维度 | 具体表现 |
|---|---|
| 网络 | 单次请求传输慢,带宽被打满 |
| CPU | DEL 大 key 会阻塞(Redis 4.0+ 可用 UNLINK 异步删) |
| 内存 | 碎片严重,内存浪费 |
| 集群 | 槽位数据倾斜 |
排查方法:
bash
# 方法一:redis-cli --bigkeys(遍历所有 key,生产慎用)
redis-cli --bigkeys
# 方法二:MEMORY USAGE 检查单个 key
MEMORY USAGE user:big:data
# 方法三:scan + memory usage 脚本扫描
redis-cli --scan --pattern '*' | xargs -I {} redis-cli MEMORY USAGE {}
治理策略:
bash
# 1. 拆分大 Key
# Hash 大 Key → 拆成多个小 Hash
HSET user:1001:base name "张三" age 28
HSET user:1001:addr city "北京" detail "朝阳区"
# List 大 Key → 切割为多个 List(按时间)
LPUSH news:2024:01:01 "news_1"
LPUSH news:2024:01:02 "news_2"
# 2. 用 UNLINK 代替 DEL(异步删除)
UNLINK big:list:key
# 3. 设计时避免大 Key
# - String > 10KB 考虑压缩或存到文件系统
# - 集合元素控制分页,不一次性全量加载
5.2 热 Key 问题
定义:某个 key 的访问量远高于其他 key(如热门商品、明星微博),导致单节点压力过大。
危害:
- 单节点 CPU 打满,影响同节点的其他 key
- 集群模式下热点所在的分片成为瓶颈
排查方法:
bash
# 方法一:redis-cli --hotkeys(Redis 4.0+)
redis-cli --hotkeys
# 方法二:MONITOR 命令(生产慎用,影响性能)
redis-cli MONITOR | grep "GET hot:key"
# 方法三:客户端统计(最推荐)
# 在 Jedis/Lettuce 客户端层统计 key 的访问频率
治理策略:
bash
# 1. 本地缓存(最有效)
# 热点数据缓存在应用进程内存中(Caffeine),减少 Redis 访问
# 2. 读写分离
# 热 Key 的读请求分散到多个从库
# 3. key 拆分(多副本缓存)
# 一个逻辑 key 拆成多个物理 key,随机访问
# 更新时需要同时更新所有副本
redis.set("hot:key:1", value, ttl)
redis.set("hot:key:2", value, ttl)
redis.set("hot:key:3", value, ttl)
// 读的时候随机选一个
String key = "hot:key:" + (ThreadLocalRandom.current().nextInt(3) + 1);
六、缓存与数据库双写一致性:完整的工业级方案
6.1 方案演进
方案一:先更 DB 后删缓存(Cache-Aside)
↓ 问题:删缓存失败怎么办?
方案二:先更 DB 后删缓存 + 重试
↓ 问题:重试还是可能失败
方案三:先更 DB 后删缓存 + 消息队列保证
↓ 问题:引入 MQ 增加复杂度
方案四:订阅 Binlog + 异步更新(最终一致)
6.2 实用的双写一致性方案
对于大部分业务场景,以下方案足够:
java
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ThreadPoolExecutor asyncExecutor;
// ========== 读:Cache-Aside ==========
public Product getById(Long id) {
String key = "product:" + id;
// 1. 查缓存
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) return product;
// 2. 查 DB(加轻量级互斥锁防击穿)
String lockKey = "lock:product:" + id;
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (locked) {
try {
// 双重检查
product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) return product;
product = productMapper.selectById(id);
if (product != null) {
// 写缓存,过期时间加随机值防雪崩
int ttl = 3600 + ThreadLocalRandom.current().nextInt(600);
redisTemplate.opsForValue().set(key, product, ttl, TimeUnit.SECONDS);
} else {
// 缓存空值防穿透
redisTemplate.opsForValue().set(key, "", 60, TimeUnit.SECONDS);
}
return product;
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 没抢到锁,短暂休眠后递归重试
try { Thread.sleep(50); } catch (InterruptedException e) {}
return getById(id);
}
}
// ========== 写:先更 DB,后删缓存,异步双删兜底 ==========
@Transactional
public void updateProduct(Product product) {
// 1. 更新数据库
productMapper.updateById(product);
// 2. 删除缓存
String key = "product:" + product.getId();
redisTemplate.delete(key);
// 3. 延迟双删(异步)
asyncExecutor.submit(() -> {
try { Thread.sleep(500); } catch (InterruptedException e) {}
redisTemplate.delete(key);
});
}
}
七、总结
本文覆盖了缓存设计的完整知识体系:
#mermaid-svg-0UppuE59PNLZIRYG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0UppuE59PNLZIRYG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0UppuE59PNLZIRYG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0UppuE59PNLZIRYG .error-icon{fill:#552222;}#mermaid-svg-0UppuE59PNLZIRYG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0UppuE59PNLZIRYG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0UppuE59PNLZIRYG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0UppuE59PNLZIRYG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0UppuE59PNLZIRYG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0UppuE59PNLZIRYG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0UppuE59PNLZIRYG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0UppuE59PNLZIRYG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0UppuE59PNLZIRYG .marker.cross{stroke:#333333;}#mermaid-svg-0UppuE59PNLZIRYG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0UppuE59PNLZIRYG p{margin:0;}#mermaid-svg-0UppuE59PNLZIRYG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0UppuE59PNLZIRYG .cluster-label text{fill:#333;}#mermaid-svg-0UppuE59PNLZIRYG .cluster-label span{color:#333;}#mermaid-svg-0UppuE59PNLZIRYG .cluster-label span p{background-color:transparent;}#mermaid-svg-0UppuE59PNLZIRYG .label text,#mermaid-svg-0UppuE59PNLZIRYG span{fill:#333;color:#333;}#mermaid-svg-0UppuE59PNLZIRYG .node rect,#mermaid-svg-0UppuE59PNLZIRYG .node circle,#mermaid-svg-0UppuE59PNLZIRYG .node ellipse,#mermaid-svg-0UppuE59PNLZIRYG .node polygon,#mermaid-svg-0UppuE59PNLZIRYG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0UppuE59PNLZIRYG .rough-node .label text,#mermaid-svg-0UppuE59PNLZIRYG .node .label text,#mermaid-svg-0UppuE59PNLZIRYG .image-shape .label,#mermaid-svg-0UppuE59PNLZIRYG .icon-shape .label{text-anchor:middle;}#mermaid-svg-0UppuE59PNLZIRYG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0UppuE59PNLZIRYG .rough-node .label,#mermaid-svg-0UppuE59PNLZIRYG .node .label,#mermaid-svg-0UppuE59PNLZIRYG .image-shape .label,#mermaid-svg-0UppuE59PNLZIRYG .icon-shape .label{text-align:center;}#mermaid-svg-0UppuE59PNLZIRYG .node.clickable{cursor:pointer;}#mermaid-svg-0UppuE59PNLZIRYG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0UppuE59PNLZIRYG .arrowheadPath{fill:#333333;}#mermaid-svg-0UppuE59PNLZIRYG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0UppuE59PNLZIRYG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0UppuE59PNLZIRYG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0UppuE59PNLZIRYG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0UppuE59PNLZIRYG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0UppuE59PNLZIRYG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0UppuE59PNLZIRYG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0UppuE59PNLZIRYG .cluster text{fill:#333;}#mermaid-svg-0UppuE59PNLZIRYG .cluster span{color:#333;}#mermaid-svg-0UppuE59PNLZIRYG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0UppuE59PNLZIRYG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0UppuE59PNLZIRYG rect.text{fill:none;stroke-width:0;}#mermaid-svg-0UppuE59PNLZIRYG .icon-shape,#mermaid-svg-0UppuE59PNLZIRYG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0UppuE59PNLZIRYG .icon-shape p,#mermaid-svg-0UppuE59PNLZIRYG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0UppuE59PNLZIRYG .icon-shape .label rect,#mermaid-svg-0UppuE59PNLZIRYG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0UppuE59PNLZIRYG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0UppuE59PNLZIRYG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0UppuE59PNLZIRYG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 缓存设计
问题域
策略域
治理域
穿透 → 布隆过滤器 + 空值缓存
击穿 → 互斥锁 + 逻辑过期
雪崩 → 随机过期 + 多级缓存 + 熔断
Cache-Aside:先更 DB 后删缓存
延迟双删兜底
淘汰策略:allkeys-lru/lfu
大 Key → 拆分 + UNLINK
热 Key → 本地缓存 + 多副本
三个黄金法则:
- 先更新数据库,再删除缓存(不要反过来)
- 过期时间加随机值(防雪崩一次配好,一劳永逸)
- 缓存不是万能的,先考虑能否不用缓存
如有疑问或指正,欢迎在评论区交流。