跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Hyperf如何应对缓存穿透导致的数据库压力_设置空结果缓存与过期时间

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

相关文章