Kill-redis-plan
本地模拟缓存击穿、缓存穿透、缓存雪崩,用于研究redis缓存
开源网站自行🔍
本地复现 Redis 缓存穿透、击穿、雪崩。
注意力在缓存层:Compose 只跑 Redis,存储用进程内 FakeDB,用 Go goroutine 打并发。
观测挂在内核上:每秒输出一个生产指标窗口,结束后汇总全程指标,由人根据关联变化判断缓存状态。
- 穿透:
penetrate查不存在的数据。缓存一直空,每次请求都打到存储。 - 击穿:
breakdown单个热点 key 过期,大量并发同时 miss,瞬间打到存储。 - 雪崩:
avalanche大批量 key 同一时刻过期,大面积 miss,存储被打满。
reports日志 观测格式
avalanche concurrency=2000 planned=2400 qps=400 duration=6s keys=200 expiry=2s
backend redis=127.0.0.1:6379 db_latency=100ms db_concurrency=20 refill_ttl=30s
time done request_total request_p99 request_errors redis_hit redis_miss redis_hit_rate redis_miss_rate redis_expired redis_errors origin_total origin_unique_keys origin_breadth origin_hot_share db_total db_saturation db_max_waiting db_wait_p99 db_found db_not_found
1s 400/2400 400 1.553ms 0 399 0 100.0% 0.0% 0 0 0 0 0.0% 0.0% 0 0.0% 0 0s 0 0
2s 794/2400 399 1.082ms 0 395 5 98.8% 1.2% 6 0 5 5 100.0% 20.0% 5 0.0% 0 0s 0 0
3s 1143/2400 401 506.879ms 0 165 235 41.2% 58.8% 194 0 235 195 83.0% 0.9% 198 96.2% 100 455.679ms 183 0
4s 1600/2400 400 556.031ms 0 400 0 100.0% 0.0% 0 0 0 0 0.0% 0.0% 37 19.4% 0 157.456ms 57 0
5s 1999/2400 399 992µs 0 400 0 100.0% 0.0% 0 0 0 0 0.0% 0.0% 0 0.0% 0 0s 0 0
6s 2400/2400 400 1.109ms 0 400 0 100.0% 0.0% 0 0 0 0 0.0% 0.0% 0 0.0% 0 0s 0 0
result status=ok elapsed=6.001s done=2400/2400 failures=0
request total=2400 errors=0 input_rejected=0 p99=505.855ms overflow=0
redis hit=2160 miss=240 hit_rate=90.00% errors=0 ops_p99=1.074ms overflow=0 expired=200 evicted=0
origin total=240 unique_keys=200 duplicate_reads=40 valid_rate=100.00%
hot_key key=item:155 total=2 share=0.83% max_inflight=2
db total=240 found=240 not_found=0 errors=0 max_waiting=100 wait_p99=455.679ms wait_overflow=0 query_p99=101.055ms query_overflow=0
具体还是可以到项目中去观察,最好来到本地跑一下,观测一下数值在面对 三大问题的变化。
数据流
一次请求:cache.Get(id) → Redis GET。
hit 则返回,FakeDB 不动。
miss 则 store.Get(占槽、睡延迟、查 map)。
有值就 Redis SET 带 TTL 再返回;没值只返回空,不写 Redis。
回源
回源= Redis Miss -> 向 DB 发起查询 (不管 Found 还是 Not Found)
回源是个动作
- Found:一次
有效回源 - Not Found:一次
无效回源
回源率 = origin_total / redis_miss
回源集中度 = origin_hot_key_total / origin_total
穿透 penetrate
Redis 返回 nil -> 回源查询 DB -> DB 返回结果为空 / 影响行数为 0 -> 缓存穿透
捕获一次就是一次穿透
穿透次数 = origin_total ∩ DB Not Found
观测:Server / 全链路监控层 中加入 Redis Miss + DB Not Found 的复合判定埋点.
-
恶意攻击:攻击者构造大量不存在的 ID(如负数 ID、随机字符串)频繁请求接口
-
业务 bug:前端传入了错误的参数,如删除了某条数据后仍然不断查询
-
爬虫扫描:遍历式爬虫尝试访问不存在的资源
-
-t unique:默认。下标 i(从 0 起)打 id1000000+i,每个 ID 只请求一次,模拟不断生成新 ID 的随机扫描。 -
-t repeat:从一组不存在 ID 中均匀抽取并打散后齐发,模拟失效链接、爬虫或攻击脚本同时反复访问一批不存在资源。默认id_pool=100;当-n较小时自动缩小池子,确保会产生重复请求。
两个模式都会齐发。开启方案 2 后,repeat 的首波仍可能在空值写入前并发回源;只有首波结束后的后续请求,才会命中短 TTL 空值缓存。
Q&A 为什么没有用redis_miss?
防击穿组件(SingleFlight / 互斥锁)的拦截:
如果有 1000 个并发请求打过来,Redis 确实记录了 1000 次 Miss。
但如果应用层配置了 singleflight,只有 1 个请求真正去查了 DB,其余 999 个在内存里挂起等待复用。
此时:Redis Miss = 1000 != 真实回源数 = 1
如果拿 Redis Miss 当回源,你会误以为数据库正承受 1000 次冲击,但实际上数据库压力只有 1。
击穿 breakdown
击穿 = 针对同一 Key 的高并发回源(origin_hot_key_max_inflight) ∩ DB Found
origin_total = 106
origin_unique_keys = 1
origin_hot_key = item:1
origin_hot_key_total = 106
origin_hot_key_max_inflight = 105
origin_hot_key_total/origin_total (回源集中度) ≈ 100%
origin_hot_key_max_inflight >> 1
针对同一 key 的高并发回源: 106 次回源全部针对 item:1,其中最多有 105 次同时进行。
热点 Key 过期/失效 -> 大量并发请求同时 Redis Miss -> 瞬间并发回源 DB -> DB 成功返回有效数据 -> 缓存击穿
预热:
把 item:1 写入 Redis(TTL 用 config 里的值),每 50ms 轮询 EXISTS,直到 key 消失;
时限为 ttl+3s,超时则失败退出。
然后 -n 个 goroutine 同时打 id 1。
回写完成前,其余请求也会 miss,叠到同一条回源上。
雪崩 avalanche
雪崩特征 = 大面积 Redis Miss ∩ 多 Key 的有效回源 ∩ DB 满载、排队与请求延迟上升
redis_expired = 200
redis_miss_rate = 56.3%
origin_total = 224
origin_unique_keys = 200
origin_breadth = 89.3%
origin_hot_share = 0.9%
db_found = 180
db_saturation = 91.4%
db_max_waiting = 100
db_wait_p99 = 453.375ms
request_p99 = 503.295ms
- 大面积 Redis Miss:200 个 key 同时过期,Redis Miss 率在该窗口升至 56.3%。、
- 多 Key 的有效回源:224 次回源覆盖 200 个不同 key,广度为 89.3%;
- 排除击穿与穿透:
db_found=180表示这些请求查到有效数据,不是缓存穿透。origin_hot_share=0.9%表示回源没有集中在单一热点 key,因此不是击穿。 - DB 满载、排队与请求延迟上升:DB 在该窗口 91.4% 的时间处于满载,最多 100 个请求等待并发槽,DB 等待 P99 为 453.375ms,最终将请求 P99 推高到 503.295ms。
- 预热:
- 先用同一个绝对过期时间预热
item:1..keys,随后立即启动固定 QPS 的持续流量: 0..ttl:缓存正常命中。- key 集体过期后:多 key 同时 miss、回源,DB 开始满载和排队。
- 回填完成后:Redis 命中恢复,DB 清空积压请求。
- 先用同一个绝对过期时间预热
文章内容来自README,非AI编写以及润色!