空结果缓存防穿透的关键在于统一入口写入、原子性保障、动态TTL及与布隆过滤器分层协同;Hyperf中需禁用直连Redis::get(),强制走Cache::get()并用'__NULL__'标记+短TTL兜底。
空结果缓存不是“查不到就塞个 null 进去”就能防穿透,关键在写入时机、过期策略和与布隆过滤器的协同位置——否则反而放大无效请求对 Redis 和 DB 的双重压力。
为什么 setex(null, 30) 在 Hyperf 里容易失效
直接在 Controller 或 Service 层判断
后执行
,会漏掉两类关键请求:
并发请求同时发现缓存 miss,全部涌向数据库,只有一条能成功写空值,其余仍穿透
未经过缓存装饰器(如
)的底层查询(如手写
),压根不会触发该逻辑
更严重的是:若空值 TTL 设为固定 30 秒,而攻击流量集中在前 2 秒打进来,DB 仍会在那 2 秒内被反复击穿。
Hyperf 中空值缓存必须绑定到缓存层统一入口
推荐使用
封装空值写入逻辑,确保所有缓存读取路径(包括
、
、手动
)都走同一套空值判定。核心要点:
Hyperf 3.1.66
本页面提供企业级 PHP 协程框架 Hyperf 3.1.66 版本的官方源码下载与完整更新日志。重点解析 v3.1.66 版本中新增的 gRPC 多客户端负载均衡支持、Pool 连接池全量刷新、Guzzle 持久化 Cookie 以及数据库 JSON 包含键查询等核心优化特性。
下载
空值写入必须带原子性保障:用
+
组合,避免并发覆盖
TTL 不应硬编码:按业务查询耗时动态计算,例如
,防止空值滞留过久
空值内容建议用特定标记(如
),而非空字符串,便于日志识别和后续灰度剔除
空值缓存和布隆过滤器怎么配合才不打架
二者不是二选一,而是分层拦截:布隆过滤器卡在
拦第一道(毫秒级判伪),空值缓存兜底第二道(覆盖布隆误判漏掉的真无效 ID)。注意三个易错点:
布隆过滤器全量构建后,若某 ID 被软删(
),不能从布隆里移除,但必须保证该 ID 查询返回空值后,立刻写入
空值缓存 key 必须和布隆校验 key 一致(如都是
),否则布隆放行后,缓存层找不到对应空值,又会打 DB
不要给空值设置永不过期:哪怕用了布隆,也要保留空值 TTL,否则内存/Redis 容量会被无效键占满
实际项目中空值缓存最容易被忽略的细节
空值缓存真正起效的前提,是它必须比任何可能绕过它的逻辑更早执行——比如自定义中间件顺序错误、协程上下文丢失、或
直接跳过了封装层。最稳妥的做法是:
禁用所有手动
对业务 key 的直连调用,强制走
在
的
中开启
,确保
能正确反序列化为
而非字符串
监控
指标(可用 Prometheus +
实现),一旦该指标突增,说明布隆漏判或空值未写入成功
$result === nullRedis::setex($key, 30, '')@CacheableDb::table()->where()Hyperf\Cache\Handler\HandlerInterfaceManager::findFromCache@CacheableCache::get()Redis::setnx($key, 'null')Redis::expire($key, 30)min(30, $dbQueryTimeMs * 3)'__NULL__'BeforeRequestBloomMiddlewarestatus = 0cache->set('user:'.$id, '__NULL__', 30)user:123Cache::driver('redis')->get()Redis::get()Cache::get()config/autoload/cache.phpoptions'serialize' => true'__NULL__'nullcache.miss.nullHyperf\Metric\Counter