hyperf存大对象前必须序列化压缩:禁用serialize(),改用json_encode()+gzdeflate()压缩并base64编码,key加:gz标记;写入前校验体积≤512kb,读取时严格按标记和魔数解压并try/catch兜底。

Hyperf里存大对象前必须做序列化压缩
Hyperf默认用serialize()序列化数据,但PHP原生序列化体积大、无压缩、不跨语言。直接把未压缩的数组或对象塞进Redis,比如一个含10万条日志的$logs数组,serialize($logs)可能产出8MB字符串,而json_encode($logs) + gzdeflate()可压到1.2MB——这直接影响maxmemory是否被快速击穿。
关键不是“能不能存”,而是“存完会不会让mem_fragmentation_ratio飙升”或触发qubf-free耗尽导致连接被关。
- 避免用
serialize(),改用json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),减少冗余字符 - 对>100KB的value,强制加
gzdeflate()(不是gzcompress(),前者无header,Redis解压更轻量) - 压缩后用
base64_encode()转义,避免二进制内容污染RESP协议(否则*、$等字节可能被误解析为RESP数组头) - 务必在key名里打标记,例如
cache:report:202605:v2:gz,方便后续排查和灰度下线
Redis客户端写入时要主动控制单次体积
Hyperf的RedisClient底层走RESP协议,输入缓冲区(input buffer)默认1GB但不可调;一旦某次SET命令携带超大payload(如未压缩的5MB JSON),服务端处理慢+网络延迟高,就容易卡在缓冲区堆积,最终触发qubf-free告警甚至断连。
这不是Redis配置问题,是Hyperf业务层没做前置校验。
- 在调用
$redis->set($key, $value)前,加一行if (strlen($value) > 512 * 1024) { throw new RuntimeException('Value too large'); } - 对List/Hash等结构,禁用
HSETALL、LRANGE key 0 -1这类全量拉取命令;改用分页+游标(HSCAN、SSCAN) - Hyperf的
CacheInterface::set()不校验大小,必须自己封装一层SafeCache类,拦截超限写入
读取时解压逻辑必须和写入严格对称
存的时候压了,取的时候忘了解,业务会拿到一串乱码;解压时没捕获gzinflate()失败,就会抛Warning: gzinflate(): data error并中断协程——Hyperf里这种错误不会自动gc_collect_cycles(),强引用一直挂着,RSS持续涨到OOM Kill。
解压不是加个函数就行,得兜住所有异常路径。
- 先检查key后缀是否含
:gz,不含则直返$raw,不调gzinflate() - 解压前用
if (substr($raw, 0, 2) === "\x1f\x8b")判断是否gzip魔数,避免对非压缩数据硬解 -
gzinflate()必须包在try/catch里,失败时记录error_log("Redis decompress failed for {$key}")并返回null,绝不让异常穿透到业务层 - Hyperf协程内不要用
unserialize()反序列化任何来自Redis的数据,一律用json_decode($str, true),避免POP链反序列化漏洞
Hyperf进程内缓存也要防大对象泄漏
很多人只盯着Redis,忘了Hyperf自己的Co\Channel、Table、DI容器属性也可能存着未释放的大对象。比如一个@Cacheable注解方法返回了10MB数组,Hyperf默认会把它整个放进协程本地缓存,下次同协程再调用直接返回——但这10MB永远不会被GC回收,因为注解生成的闭包隐式持有了$this和全部上下文。
这个泄漏在memory_get_usage()里完全看不到,但ps aux --sort=-rss能清楚看到RSS单向爬升。
- 禁用
@Cacheable对大结果的缓存,改用显式$this->redis->get($key)+ 手动解压 - 所有存入
Co\Channel或Table的数据,必须限制单条strlen() - 在
@OnWorkerStart里注册的全局监听器,回调函数严禁use ($bigObject),改用ID传参后按需查Redis
Hyperf里真正危险的从来不是Redis内存溢出,而是PHP层序列化+Redis缓冲区+协程强引用三者叠加——其中任意一环没断,mem_fragmentation_ratio就可能从1.2一路飙到3.0,而你还在看used_memory_human觉得“才200MB,还早”。











