不能直接用MemoryDriver作一级缓存,因其为进程级单例、无协程隔离、不支持TTL自动清理,易致内存泄漏、请求污染与不一致;应使用CoroutineMemoryDriver,绑定协程生命周期,实现请求内免Redis往返。

Hyperf 实现多级缓存不是“加一层 Redis 再加一层 Caffeine”就行,而是必须按协程生命周期、数据访问模式和失效粒度分层设计。本地缓存不能简单套用传统 JVM 的 Caffeine 思路,否则会引发内存泄漏、协程污染或缓存不一致。
为什么不能直接用 MemoryDriver 做一级缓存
Hyperf 的 MemoryDriver 是进程级单例,所有协程共享同一份内存 —— 这在常驻进程的 Swoole 环境下极易导致:缓存堆积不释放、不同用户请求互相污染、TTL 失效逻辑错乱。真实生产中,误用 MemoryDriver 作通用一级缓存,三天内必出现内存持续上涨、GC 频繁、响应毛刺。
- 它适合存框架元数据(如路由表),不适合业务数据
- 没有协程隔离,
cache()->get('user:1001')在协程 A 写入后,协程 B 可能意外读到 - 不支持 TTL 自动清理,得靠手动
clear()或重启进程,不可控
正确的一级缓存:用 CoroutineMemoryDriver + 请求级生命周期
真正安全的一级缓存是 CoroutineMemoryDriver,它把数据绑定在当前协程上下文里,请求结束自动销毁,零配置、无污染、免同步。它不解决跨请求复用,但恰恰是热 Key 场景最需要的 —— 同一请求内多次查 config:site、user:perms:{uid},只走一次 Redis。
- 配置示例:
'co' => ['driver' => Hyperf\Cache\Driver\CoroutineMemoryDriver::class] - 使用方式:
cache()->driver('co')->get($key),或配合注解@Cacheable(ttl=0)+cache()->driver('co') - 注意:不能用于需跨请求共享的场景(如商品库存计数),那是 Redis 的职责
二级缓存必须区分数据类型配不同 TTL 和驱逐策略
把所有业务数据都塞进同一个 Redis 缓存池、设统一 TTL,是 Hyperf 多级缓存崩坏的起点。报表模板、用户配置、订单列表三类数据,更新频率、体积、访问密度完全不同,混在一起必然导致:大 Key 拖慢 pipeline、热 Key 抢占连接、过期风暴冲击 DB。
- 模板类(
tpl:前缀):TTL 设7200(2 小时),用CacheEvict按group批量删除,避免单 key 更新触发全量 reload - 查询结果类(
rpt:前缀):TTL 控制在300(5 分钟)内,key 中必须含参数指纹(如rpt:orders:uid_123:status_paid:page_1),防缓存污染 - 用户基础信息(
user:info:):TTL600,搭配互斥锁防击穿,避免 Redis 过期瞬间大量请求打穿 DB
本地表(SwooleTable)不是必须项,但对超高频小数据有效
文档里常见的 SwooleTableManager 方案,只适用于极窄场景:单进程内高频读写、value ≤1KB、无复杂结构、可接受进程重启即丢失。比如秒杀倒计时、开关配置、灰度分流规则。一旦 value 超过 2KB,序列化/反序列化开销就抵消了内存优势;若数据含对象引用或闭包,serialize/unserialize 还可能失败。
- 别用它存用户 profile、订单列表等结构化大数据
-
SwooleTable::column('value', Table::TYPE_STRING, 1024)是硬限制,超长截断无声失败 - 务必在
server.php的callbacks中初始化,否则协程里调用getInstance()返回 null
多级缓存真正的难点不在“怎么写”,而在“哪一层该管什么”。本地缓存不是越厚越好,而是越准越好 —— 准确识别热 Key 的访问边界(协程内 / 请求内 / 全局)、准确匹配数据变更节奏(秒级刷新 vs 小时级更新)、准确控制失效半径(单 key / group / 全量)。漏掉其中任一环,多级缓存就会变成多级负担。


















