怎样防止Redis穿透攻击引发数据库连接池耗尽_增加连接池扩容策略

作者:袖梨 2026-07-09
单纯扩容数据库连接池不能解决缓存穿透,必须在Redis层用空值缓存(如SETEX key 30 "")、布隆过滤器(需全量初始化+误判兜底)和接口层参数校验三者至少落地两项。

单纯扩容数据库连接池不能解决缓存穿透,反而会掩盖真实问题、加剧资源争抢。 穿透请求打到数据库时,哪怕连接池从 50 扩到 500,只要请求持续构造无效 user_idproduct_sku,连接照样会被占满、超时堆积、触发连接泄漏或事务卡死。真正要拦,得在 Redis 层就让这些请求“止步”。

空值缓存必须设 TTL,且不能用永久存储

空值缓存是防御穿透最直接有效的手段,但很多人栽在 TTL 设置上:

  • 不设过期时间(如用 SET key "")→ 空值永久驻留,Redis 内存缓慢上涨,最终 OOM
  • 过期时间设太长(如 24 小时)→ 无效 key 长期占用内存,且无法响应业务变更(比如某类 ID 规则已弃用)
  • 过期时间设太短(如 1 秒)→ 拦不住并发请求,多个线程几乎同时查到空,仍会重复打库

推荐值:对大多数业务,SETEX key 30 ""(30 秒)是平衡点。它足够挡住突发扫描,又不会长期污染缓存。PHP 中用 $redis->setex($key, 30, '');Java RedisTemplate 用 opsForValue().set(key, "", 30, TimeUnit.SECONDS)

布隆过滤器不是“加了就完事”,得管住初始化和误判兜底

布隆过滤器能前置拦截 99% 的无效 key,但实际落地常忽略两点:

  • 初始化阶段没全量加载合法 key → 过滤器形同虚设。例如用户表有 800 万记录,只导入前 10 万,漏掉的 key 全部穿透
  • 遇到误判(bf.exists('product_bf', '123456789') 返回 True,但 DB 实际无此商品)→ 后续流程没做空值缓存,导致该 key 下次仍穿透

务必补上兜底:即使布隆说“可能存在”,查库为空后,仍要走一遍空值缓存逻辑。Python 示例中,bf_client.exists() 返回 False 直接拒掉;返回 True 后,DB 查询为空 → 立即 redis.setex('user:123456789', 30, '')

接口层校验比缓存层拦截更早、更省资源

很多穿透源于参数本身非法,比如 user_id=abcorder_no=(空)、sku=999999999999999999999(超出位数)。这类请求根本不该进 Redis:

  • PHP 中用 filter_var($userId, FILTER_VALIDATE_INT) 或正则 /^d{1,10}$/ 提前拦截
  • Spring Boot 用 @Pattern(regexp = "^d{1,10}$") 注解校验入参
  • 网关层(如 Nginx、Kong)配置 if ($args ~* "user_id=[^0-9]") { return 400; }

这一层拦截成功,连 Redis connect 都省了,CPU 和网络开销直接归零。别等请求跑到缓存再处理。

数据库连接池耗尽时,光看“连接数满”会误判根本原因

当监控显示 HikariCP - ActiveConnections=50/50 或 MySQL Threads_connected 持续高位,第一反应不该是调大池子,而是查三件事:

  • 查慢日志:是否存在 SELECT * FROM user WHERE id = ? 绑定参数为 null 或极长字符串,导致全表扫描
  • 查应用线程堆栈:是否有大量线程卡在 JDBCStatement.execute(),说明 SQL 本身执行慢,不是连接不够
  • 查 Redis info stats:若 rejected_connections 在涨,说明穿透已严重到 Redis 自身都开始拒绝新连接,此时扩 DB 连接池毫无意义

穿透引发的连接池耗尽,本质是“无效请求 + 无防护逻辑”双击打穿。修复必须回到请求入口——校验、布隆、空值,三者至少落地两项。连接池只是最后一道缓冲,不是保险丝。

相关文章

精彩推荐