缓存穿透、击穿、雪崩这三个词几乎人人听过,但它们各自的量级是多少?常见的防线真的有效吗?这篇用 LocMem + 线程 + 一个睡 50 ms 的「数据库」把三种情形各做一遍,数出数据库被调用了多少次。
不缓存空结果 对同一个不存在的 key 请求 100 次 → 数据库调用 100 次 缓存空结果(短 TTL) 对同一个不存在的 key 请求 100 次 → 数据库调用 1 次 缓存空结果,但每次请求的 key 都不同(随机 key 攻击)→ 数据库调用 100 次(空值缓存挡不住,需要布隆过滤器或参数校验,本文没有测)
「请求的 key 数据库里也没有」,所以缓存永远填不上。缓存空值(短 TTL)能挡住重复请求同一个不存在的 key;但随机 key 每个都是第一次,只能靠参数校验、布隆过滤器等前置手段。
无保护 热点 key 已过期,50 个并发请求 → 数据库调用 50 次;最慢一次 55 ms;总耗时 56 ms 互斥锁(cache.add) 热点 key 已过期,50 个并发请求 → 数据库调用 1 次;最慢一次 62 ms;总耗时 63 ms
互斥锁的写法:cache.add(lock, 1, 5) 成功者去回源,失败者轮询缓存。注意给锁设过期时间,防止拿锁的线程崩溃后锁永不释放;并发极高时轮询本身也有开销(本文没有测)。
相同 TTL 4 秒内数据库调用共 838 次;每 250 ms 窗口的最高调用数 189(窗口序列前 16 个:[0, 0, 0, 0, 0, 11, 189, 147, 30, 0, 0, 0, 128, 163, 148, 22]) TTL 抖动 ±1s 4 秒内数据库调用共 1415 次;每 250 ms 窗口的最高调用数 122(窗口序列前 16 个:[0, 0, 76, 98, 73, 94, 122, 108, 117, 110, 108, 84, 98, 112, 103, 112])
相同 TTL 的 key 同时批量写入(比如启动时预热),会同时过期。抖动能把脉冲摊平(峰值下降),但不减少总量;真实系统里还要配合限流、降级(本文没有做)。