cache() 存的数据没变小反而更占内存,是因为thinkphp默认用serialize()序列化后直接存储,未压缩,导致体积几乎翻倍且带php类型标记;真正压缩需在序列化后、写入前手动干预,如重写redis驱动的set()/get()方法添加gzdeflate()/gzinflate()。

为什么 cache() 存的数据没变小,反而更占内存?
ThinkPHP 默认用 File 或 Redis 驱动缓存时,cache() 写入的值是直接 serialize() 后存的,没压缩。哪怕你存的是大数组或 JSON 字符串,它也原样序列化——体积几乎翻倍,还带 PHP 类型标记。尤其在 Redis 里,这会让 MEMORY USAGE 明显升高。
真正起作用的压缩,得在序列化之后、写入之前做。TP 自身不提供开箱即用的压缩开关,必须手动干预序列化流程。
- 别指望配置
'compress' => true(TP5/6 都不认这个) -
File驱动下,压缩后文件虽小,但每次读取要额外解压+反序列化,CPU 开销略升 -
Redis驱动下,压缩对网络传输和内存都友好,但需确保服务端没禁用zlib(PHP 本身必须启用zlib扩展)
怎么让 cache() 写入前自动压缩?
核心是重写缓存驱动的 set() 和 get() 方法。以 Redis 驱动为例:继承原 think\cache\driver\Redis,覆盖两个方法,在序列化后加 gzdeflate(),反序列化前加 gzinflate()。
示例关键片段:
class CompressedRedis extends \think\cache\driver\Redis
{
protected function set($key, $value, $expire = null)
{
$value = gzdeflate(serialize($value));
return parent::set($key, $value, $expire);
}
protected function get($key)
{
$value = parent::get($key);
return $value === false ? false : unserialize(gzinflate($value));
}
}
- 必须检查
$value是否为false,否则gzinflate(false)会报 Warning - 压缩率取决于数据类型:纯文本、JSON、稀疏数组效果明显;已加密或二进制内容可能越压越大
- 别用
gzencode()——它带 header,解压时必须用gzdecode(),而 TP 的serialize()结果不含 header,兼容性差
File 缓存压缩要注意哪些路径和权限问题?
File 驱动本身不支持压缩,但你可以通过自定义驱动把 gzdeflate() 套进去。此时文件后缀不再是 .php,得改成 .gz 或其他标识,否则 TP 默认用 include 加载,会出错。
- 缓存目录(如
runtime/cache/)需保持写权限,且 PHP 要能创建.gz文件 - 务必修改
get()中的文件读取逻辑:用file_get_contents()+gzinflate(),不能依赖 TP 原生的include加载机制 - 避免和原生
File驱动混用——同一目录下既有.php又有.gz文件,清理逻辑会失效
压缩后 cache() 的 TTL 和命中率还准吗?
压缩本身不影响过期时间(TTL),TP 仍按原始 $expire 设置 Redis 的 EXPIRE 或文件的 mtime。但有两个隐性风险:
- 如果压缩后数据损坏(比如
gzinflate()失败),get()会返回false,表现为“缓存未命中”,实际是解压失败 - 高频写入场景下,反复
gzdeflate()+gzinflate()可能放大 CPU 波动,建议对 >100KB 的值才启用压缩 - Redis 中用
GET查看压缩值是乱码,调试时记得先gzinflate()再unserialize(),别误判为存储异常
压缩不是银弹。数据小(
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











