【无标题】Redis 从入门到精通(七):缓存设计与最佳实践 —— 穿透、击穿、雪崩与一致性终极指南

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 → 本地缓存 + 多副本

三个黄金法则

  1. 先更新数据库,再删除缓存(不要反过来)
  2. 过期时间加随机值(防雪崩一次配好,一劳永逸)
  3. 缓存不是万能的,先考虑能否不用缓存

如有疑问或指正,欢迎在评论区交流。

相关推荐
睡不醒男孩0308231 小时前
达梦数据安装详细步骤(包含CLup一键部署达梦数据库实例)
数据库·达梦·clup
念何架构之路1 小时前
存储技术Redis
数据库·redis·缓存
淘源码d1 小时前
医院专业级PACS系统完整源码(C+VC+MSSQL)
c语言·数据库·sqlserver·源码·pacs系统·医学影像系统
wu8587734571 小时前
向量数据库不是银弹:从枚举漏检到 ReACT 多轮召回的实践路径
前端·数据库·react.js
hsg772 小时前
简述:Jensen Huang‘s Footsteps网站全内容分析
前端·javascript·数据库
yuezhilangniao2 小时前
MySQL 8.0.32 二进制安装脚本 和初始化 操作系统版本rocky86
数据库·mysql·adb
Trouvaille ~2 小时前
【Redis篇】Redis 主从复制:数据同步的原理与实现
数据库·redis·缓存·中间件·高可用·主从复制·后端开发
真实的菜3 小时前
Redis 从入门到精通(五):哨兵模式(Sentinel)—— 自动故障转移的完整原理与实战
数据库·redis·sentinel
Solis程序员3 小时前
缓存三剑客预防策略
java·spring·缓存