2、缓存穿透保护算法总览:布隆过滤器、空值缓存、限流降级、接口校验
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都无法命中,导致每次请求都直接穿透到后端数据库。在高并发场景下,这种无效请求会瞬间压垮数据库。本章从四种主流保护算法出发,梳理其原理、适用场景与组合策略。
2.1 四种保护算法对比总览
| 保护算法 | 核心原理 | 拦截时机 | 内存开销 | 误判率 | 典型场景 |
|---|---|---|---|---|---|
| 布隆过滤器 | 位数组 + 多个哈希函数,快速判断 key 是否存在 | 缓存查询前 | 低(固定位图) | 有(可接受) | 海量 key 过滤、黑名单校验 |
| 空值缓存 | 将查询结果为空的 key 也缓存起来,设置短 TTL | 缓存回源后 | 中(随空 key 增长) | 无 | 热点空 key、临时性穿透 |
| 限流降级 | 控制单位时间请求量,超出阈值直接拒绝或返回默认值 | 请求入口 | 极低(计数器) | 无 | 突发流量、系统过载保护 |
| 接口校验 | 在业务层对请求参数进行合法性校验,拦截非法 key | 业务逻辑入口 | 无 | 无 | 参数格式校验、ID 范围校验 |
选型建议:布隆过滤器适合大规模 key 预判,空值缓存适合小规模热点空 key,限流降级是兜底手段,接口校验是成本最低的第一道防线。实际生产环境通常组合使用 2~3 种算法。
2.2 布隆过滤器:概率型前置过滤
布隆过滤器(Bloom Filter)由一个很长的二进制位数组和多个哈希函数组成。当插入一个 key 时,通过 k 个哈希函数计算出 k 个位索引,并将这些位全部置为 1。查询时,同样计算 k 个位索引,如果所有位都是 1,则 key 可能存在;如果任意一位为 0,则 key 一定不存在。
- 优点:空间效率极高,插入和查询时间复杂度均为 O(k)。
- 缺点:存在误判率(假阳性),且无法删除元素(标准实现)。
- 工程参数:误判率 p 通常设为 1%~5%,位数组长度 m 和哈希函数个数 k 根据预估元素量 n 计算。
// 布隆过滤器核心参数估算公式(近似)
m = - (n * ln(p)) / (ln(2)^2)
k = (m / n) * ln(2)
工程案例:某电商商品详情页,商品 ID 为 9 位数字,总量约 1 亿。使用布隆过滤器(误判率 1%)仅需约 120 MB 内存,即可拦截 99% 以上的穿透请求。
2.3 空值缓存:兜底回写策略
当数据库查询结果为空时,仍然将这个 key 写入缓存,但设置一个较短的过期时间(通常 30~60 秒)。后续相同 key 的请求直接返回空值,不再穿透到数据库。
- 优点:实现简单,对现有代码侵入小,能有效拦截重复空 key 穿透。
- 缺点:占用额外缓存空间,且无法拦截第一次穿透(第一次仍需查库)。
- 优化方向:结合布隆过滤器使用,布隆过滤器拦截大部分,空值缓存兜底剩余误判。
// 空值缓存伪代码示例
public Object getWithNullCache(String key) {
Object value = cache.get(key);
if (value != null) {
return value; // 缓存命中,直接返回
}
// 缓存未命中,查数据库
value = db.query(key);
if (value == null) {
// 空值也缓存,TTL 设为 30 秒
cache.set(key, NULL_PLACEHOLDER, 30);
} else {
cache.set(key, value, 3600);
}
return value;
}
2.4 限流降级:系统级熔断保护
限流降级不直接解决穿透问题,而是防止穿透流量过大导致系统崩溃。当单位时间请求量超过阈值时,直接拒绝请求或返回降级结果(如默认值、缓存旧数据)。
| 限流算法 | 特点 | 适用场景 |
|---|---|---|
| 固定窗口计数器 | 实现简单,存在临界突变 | 低精度限流 |
| 滑动窗口日志 | 精度高,内存开销大 | 精确限流 |
| 漏桶算法 | 平滑流量,允许突发 | 流量整形 |
| 令牌桶算法 | 允许一定突发,常用 | 通用限流 |
注意:限流降级是最后一道防线,不能替代布隆过滤器和空值缓存。如果系统频繁触发限流,说明前置过滤策略需要优化。
2.5 接口校验:成本最低的第一道防线
在业务入口层对请求参数进行合法性校验,是最简单、最有效的穿透防护手段。很多穿透攻击正是利用参数漏洞(如负数 ID、超长字符串、特殊字符)来绕过缓存。
- 常见校验规则:
- ID 范围校验(如 ID > 0 且 ID < 最大值)
- 参数格式校验(正则匹配、长度限制)
- 业务规则校验(如订单状态、用户权限)
- 优势:零内存开销,无误判,直接拦截非法请求。
- 局限:只能拦截格式异常请求,无法拦截合法但不存在的数据。
// 接口校验示例
public Response queryProduct(String productId) {
// 1. 参数非空校验
if (productId == null || productId.isEmpty()) {
return Response.fail("参数不能为空");
}
// 2. 格式校验:必须为纯数字,长度 6~10 位
if (!productId.matches("\\d{6,10}")) {
return Response.fail("参数格式错误");
}
// 3. 业务校验:产品 ID 必须在有效范围内
long id = Long.parseLong(productId);
if (id < 100000 || id > 9999999999L) {
return Response.fail("参数超出范围");
}
// 通过校验,继续业务逻辑
return processQuery(id);
}
2.6 四种算法组合策略
单一算法无法覆盖所有穿透场景,生产环境通常采用分层组合策略:
- 第一层(接口校验):拦截格式异常、范围异常的非法请求。
- 第二层(布隆过滤器):拦截大部分不存在的 key,降低缓存层压力。
- 第三层(空值缓存):兜底布隆过滤器的误判 key,防止穿透到数据库。
- 第四层(限流降级):在极端流量下保护系统整体可用性。
最佳实践:某社交平台 Feed 流系统采用“接口校验 + 布隆过滤器 + 空值缓存”三层组合,穿透率从 12% 降至 0.03%,数据库 QPS 下降 85%。限流降级作为应急开关,仅在极端流量下触发。