空值缓存ttl应设为60~300秒,禁用null/false而用''或{"empty":true};布隆过滤器须动态更新、不可静态;参数校验必须前置;限流需分三级识别攻击。

缓存空值必须设短 TTL,否则内存爆炸
数据库查不到数据时,直接 Redis::setex($key, 3600, '') 是典型错误——空值缓存一小时,恶意请求扫 10 万个非法 ID,Redis 就堆满 10 万条无效键。真实线上应控制在 60~300 秒之间。
- 业务 ID 类(如
user_12345)建议60秒;字符串类(如用户名、手机号)可放宽到180秒 - 空值内容别用
null或false,PHP 的get返回false可能和连接失败混淆,统一用空字符串''或 JSON 字符串'{"empty":true}' - 务必检查 Redis 内存使用率:如果
used_memory_human接近配置上限,且keys *empty*占比突增,说明空值 TTL 设太长了
布隆过滤器不能只“查”,还得“动态补全”
很多 PHP 工程师把布隆过滤器当成静态白名单——初始化时塞一次合法 ID,之后再也不更新。但用户注册、后台导入等操作会让新 ID 持续产生,旧过滤器立刻失效,导致大量“真存在却被拦截”的误拒。
- 每次成功写入数据库后,必须同步调用
$bloomFilter->add($key),比如注册完用户立即加user_99999 - 不要用单机数组模拟布隆过滤器,高并发下无法共享状态;推荐用 RedisBitmap 或扩展如
ext-bloom,或封装成独立服务 - 误判率不是越低越好:
0.1%误判需 10 倍内存,而1%误判对大多数业务无感,但内存节省明显
参数校验必须在布隆过滤器之前执行
布隆过滤器再快,也是额外开销。真正该先拦住的,是明显非法的请求,比如 id=-1、id=abc、id=1e100 这类根本不可能存在的输入。
- 用
filter_var($id, FILTER_VALIDATE_INT)+$id > 0组合,比任何缓存逻辑都快 - 对字符串类 key(如订单号),用
strlen($sn) === 16 && ctype_alnum($sn)比哈希进布隆过滤器省 90% CPU - 这一层校验失败,直接返回
400 Bad Request,连日志都不用记——避免给攻击者反馈
限流要区分“空值穿透”和“正常高频”
单纯按 IP 限流会误伤爬虫或企业客户;只按接口限流又挡不住单个 IP 扫描不同 ID。真实场景需要分层识别:
立即学习“PHP免费学习笔记(深入)”;
- 一级:参数校验失败的请求,按
invalid_param:{$ip}计数,阈值5/分钟即封1小时 - 二级:布隆过滤器返回“不存在”但参数合法的请求,按
empty_key:{$ip}计数,阈值100/5 分钟即触发熔断,返回429 Too Many Requests - 三级:命中缓存但为空值的请求,不计数——说明攻击已失效,无需干预
布隆过滤器和空值缓存不是二选一,而是漏斗:参数校验拦掉 70%,布隆过滤器再拦掉 25%,剩下 5% 才靠空值缓存兜底。漏掉任一环,防护就断档。











