单纯扩容数据库连接池不能解决缓存穿透,必须在Redis层用空值缓存(如SETEX key 30 "")、布隆过滤器(需全量初始化+误判兜底)和接口层参数校验三者至少落地两项。
单纯扩容数据库连接池不能解决缓存穿透,反而会掩盖真实问题、加剧资源争抢。 穿透请求打到数据库时,哪怕连接池从 50 扩到 500,只要请求持续构造无效 user_id 或 product_sku,连接照样会被占满、超时堆积、触发连接泄漏或事务卡死。真正要拦,得在 Redis 层就让这些请求“止步”。
空值缓存是防御穿透最直接有效的手段,但很多人栽在 TTL 设置上:
SET key "")→ 空值永久驻留,Redis 内存缓慢上涨,最终 OOM推荐值:对大多数业务,SETEX key 30 ""(30 秒)是平衡点。它足够挡住突发扫描,又不会长期污染缓存。PHP 中用 $redis->setex($key, 30, '');Java RedisTemplate 用 opsForValue().set(key, "", 30, TimeUnit.SECONDS)。
布隆过滤器能前置拦截 99% 的无效 key,但实际落地常忽略两点:
bf.exists('product_bf', '123456789') 返回 True,但 DB 实际无此商品)→ 后续流程没做空值缓存,导致该 key 下次仍穿透务必补上兜底:即使布隆说“可能存在”,查库为空后,仍要走一遍空值缓存逻辑。Python 示例中,bf_client.exists() 返回 False 直接拒掉;返回 True 后,DB 查询为空 → 立即 redis.setex('user:123456789', 30, '')。
很多穿透源于参数本身非法,比如 user_id=abc、order_no=(空)、sku=999999999999999999999(超出位数)。这类请求根本不该进 Redis:
filter_var($userId, FILTER_VALIDATE_INT) 或正则 /^d{1,10}$/ 提前拦截@Pattern(regexp = "^d{1,10}$") 注解校验入参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 本身执行慢,不是连接不够info stats:若 rejected_connections 在涨,说明穿透已严重到 Redis 自身都开始拒绝新连接,此时扩 DB 连接池毫无意义穿透引发的连接池耗尽,本质是“无效请求 + 无防护逻辑”双击打穿。修复必须回到请求入口——校验、布隆、空值,三者至少落地两项。连接池只是最后一道缓冲,不是保险丝。