[igbinary/igbinary]缓存体积砍一半,反序列化快一倍的隐形加速器

2026-06-22 奥古斯宏 #PHP #igbinary #序列化 #缓存 #性能优化
你的 Redis 缓存可能浪费了一半空间。换上这个 700+ Star 的 PHP 扩展,序列化体积砍 70%,反序列化快 2 倍,Nextcloud 默认启用,Moodle 深度集成。

PHP 默认的 serialize() 在缓存场景下其实是个隐形浪费——把一个 100 行的数据库 rowset 拍成字符串,光重复的 key 名(idnameemail)就占掉一半体积。我跑了一组真实数据:同一个用户列表,serialize 出来 32866 字节,换 igbinary 只有 8946 字节,直接砍掉 73%。反序列化时,igbinary 比 unserialize 还快 2 倍。

igbinary 是 PHP 的一个 C 扩展(发布在 PECL 上)。名字里的 IG 来自 IRC-Galleria——芬兰的一个社交网站,这个扩展当年就是为它的缓存序列化设计的。最初由 Oleg Grenrus 在 2008 年开源,2011 年起仓库迁到 igbinary 组织继续维护(原作者 phadej 的个人仓库最后一次更新是 2014 年),近年主力维护者是 Tyson Andre(2017-2025 年间最活跃),2026 年起 Arndt Kaiser 和 Teddy Grenman 接力。它做的事情很专一:用更紧凑的二进制格式替代 serialize(),而且 API 对应用层透明,大多数场景配个 ini 就完事。Nextcloud 检测到可用就默认开,Moodle 深度集成到缓存系统,Symfony Cache 和 Phalcon 作为可选序列化器。

igbinary GitHub 仓库首页

GitHub 上 781 Star、74 Fork,BSD-3-Clause 协议。截图顶部那个 "forked from phadej/igbinary" 容易让人误解——phadej 就是 Oleg Grenrus 的个人 GitHub 账号,他停更之后由 igbinary 组织接管,现在已经领先原仓库 547 个 commit。GitHub 标记的 fork 关系只是历史遗留,实质上的主仓库就是 igbinary/igbinary,PECL 官方页面引用的也是这个。

安装

# 方式 1: PECL(推荐)
pecl install igbinary

# 方式 2: 包管理器(Debian/Ubuntu 源里版本可能滞后)
# apt install php-igbinary

# 方式 3: 源码编译
phpize && ./configure --enable-igbinary && make && make install

装完在 php.ini 里加一行:

extension=igbinary.so

Windows 从 PECL 页面 下载对应 PHP 版本的 DLL,改名 php_igbinary.dll 放到 ext/ 目录,同样加 extension=igbinary

兼容性方面,3.x 系列支持 PHP 7.0 到 8.5,老项目用 2.x 系列(PHP 5.2 - 7.3)。当前稳定版 3.2.16,3.2.17RC1 跟进了 PHP 8.5。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

igbinary 在 PECL 的页面

PECL 是 igbinary 的官方发布渠道,所有版本都在这里。最新 3.2.17RC1 发布于 2025-11-27,四位 lead maintainer:Oleg Grenrus(原作者)、Pierre Joye、Teddy Grenman、Tyson Andre。

一分钟用法:就两个函数

整个用户空间 API 只有 igbinary_serialize()igbinary_unserialize() 这两个函数。没别的,别被网上以讹传讹的 igbinary_load / igbinary_dump 误导,这俩函数根本不存在(我去翻了源码 src/php7/igbinary.c 确认过,扩展注册的 PHP 函数表里就这两条)。

<?php
$data = [
    ['id' => 1, 'name' => 'Alice', 'tags' => ['php', 'redis']],
    ['id' => 2, 'name' => 'Bob',   'tags' => ['php', 'mysql']],
];

// 序列化
$bin = igbinary_serialize($data);

// 反序列化
$restored = igbinary_unserialize($bin);

完整兼容 __sleep / __wakeup / __serialize / __unserialize / Serializable,支持循环引用,行为跟 serialize 完全一致。下面的例子验证 __sleep 被正确调用(序列化时排除敏感字段):

<?php
class User {
    public string $name;
    public string $password;  // 敏感字段
    public function __sleep(): array {
        return ['name'];  // 只序列化 name
    }
}

$u = new User();
$u->name = 'alice';
$u->password = 'super-secret-pwd';

$bin = igbinary_unserialize(igbinary_serialize($u));
echo $bin->name;     // 'alice'
// password 没有出现在二进制里,反序列化时不还原

真实 benchmark:体积砍 73%,反序列化快 2 倍

benchmark 真实输出

测试数据是 100 个用户记录,每个记录有 6 个字段加一个 profile 子对象——典型的数据库 rowset 结构。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

体积对比

序列化方式 体积 相对 PHP serialize
PHP serialize 32866 B 基线
igbinary 8946 B -73%
json_encode 24069 B -27%

igbinary 的体积优势主要来自两点:

  1. 重复字符串去重(compact_strings 默认开启):100 条记录里重复出现的 idnameemailrole 这些 key,以及重复的值(比如 memberactive、默认头像 URL),只在二进制里存一份,后续用引用索引。
  2. 二进制编码而非文本:整数、布尔、字符串长度用紧凑的二进制表示,不再写 s:4:"name" 这种冗余格式。

速度对比(10000 次迭代)

操作 PHP igbinary json
序列化 0.1574s 0.1758s 0.2953s
反序列化 0.2916s 0.1434s 0.8094s

反序列化快 2.03 倍,序列化稍慢(compact_strings 去重的哈希表开销)。缓存是"写一次读多次",这个交换正合适。

compact_strings 开关:核心优势的来源

igbinary.compact_strings 是这个扩展唯一专属的 ini 配置,开关效果实测:

配置 体积 serialize 速度
compact_strings=Off 21982 B 0.1030s
compact_strings=On(默认) 8946 B 0.1773s

关闭后体积优势从 73% 缩到 33%,但 serialize 反而变快了(省掉了哈希表维护开销)。如果你的缓存场景对体积不敏感(比如纯内存缓存),可以考虑关掉换序列化速度;但 Redis/Memcached 这种网络存储,强烈建议保持默认 On,网络 IO 的节省远大于序列化本身。

二进制格式可视化

光看字节数太抽象,直接看一段二进制。

PHP serialize 与 igbinary 的字节级对比

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

['first' => 'Alice', 'active' => true, 'tags' => ['a', 'b', 'a']] 用 igbinary 序列化:

000000021403110566697273741105416c69636511066163746976650511047461677314030600110161060111016206020e04

逐字节拆解:

偏移 字节 含义
0 00 00 00 02 magic header + format version 2
4 14 ARRAY 类型
5 03 数组 3 个元素
6 11 05 66 69 72 73 74 STRING 长度 5 = "first"
13 11 05 41 6c 69 63 65 STRING 长度 5 = "Alice"
20 11 06 61 63 74 69 76 65 STRING 长度 6 = "active"
28 05 BOOL true
... ... 后面是 tags 数组,注意末尾的 0e04 是引用索引——指向第 0 个 'a'

整段 51 字节,PHP serialize 同样数据 100 字节。注意末尾的 0e04,这是 igbinary 在表示 tags => ['a', 'b', 'a'] 里的第二个 'a' 时,直接引用前面出现过的 'a',不再写一遍字符串。

集成场景:配 ini 或一行 setOption

Session:一行 ini

extension=igbinary.so
session.serialize_handler=igbinary

把默认的 php handler 换成 igbinary,Session 文件体积会下降。我实测同样一份 session 数据($_SESSION = ['user_id' => 42, 'cart' => ['item1', 'item2'], 'last_login' => '2026-06-20 12:00:00']),php handler 写 56 字节,igbinary handler 写 43 字节,省 23% 左右——session 数据通常不大且 key 名重复少,体积优势不像 rowset 数据那么夸张(后者能砍 70%+)。

重要坑:切换 handler 后,老的 session 文件全部失效,用户会被强制重新登录。生产环境切换前要么等所有 session 自然过期,要么主动清空 session 目录(/var/lib/php/sessions/ 之类)。这是因为不同 handler 的二进制格式互不兼容。

APCu:一行 ini

apc.serializer=igbinary

这个 ini 是 INI_SYSTEM,只能在 php.ini 里设,不能用 ini_set。改完要重启 PHP-FPM。APCu 5.1.10+ 才稳定支持。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

Redis:phpredis 扩展

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 启用前先检测常量是否定义
if (defined('\\Redis::SERIALIZER_IGBINARY')) {
    $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
} else {
    $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
}

$redis->set('user:1', $userArray);  // 自动 igbinary 序列化
$user = $redis->get('user:1');      // 自动反序列化,拿到原数组

:Redis::SERIALIZER_IGBINARY 常量存在的前提是 phpredis 编译时启用了 igbinary 序列化器支持。即便系统里 igbinary 已经装好,pecl install redis 默认还是不带——安装时会问 "enable igbinary serializer? [no]",默认就是 no。我实测默认装完后 defined('Redis::SERIALIZER_IGBINARY') 返回 false,必须 printf 'yes\nno\n...' | pecl install redis 显式答 yes 才会带上。源码编译对应 --enable-redis-igbinary 配置选项。常量未定义时直接 setOption 会 fatal error,所以使用前必须检测。

Memcached:libmemcached 扩展

$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);

// HAVE_IGBINARY 是编译期常量,大于 0 才能用
if (Memcached::HAVE_IGBINARY > 0) {
    $memcached->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY);
}

跟 Redis 类似,libmemcached 编译时也要带 igbinary 支持。Nextcloud 在 config.sample.php 里把这个选项默认注释掉,提示"启用前确保 igbinary 模块可用"。

Symfony Cache:DefaultMarshaller 默认不开 igbinary

Symfony Cache 的 DefaultMarshaller 默认走 PHP serialize(),要显式传 true 才切到 igbinary:

// symfony/cache 8.2, DefaultMarshaller.php
public function __construct(?bool $useIgbinarySerialize = null, ...)
{
    $this->useIgbinarySerialize = true === $useIgbinarySerialize;
}

用法:

use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Adapter\RedisAdapter;

$marshaller = new DefaultMarshaller(true);
$pool = new RedisAdapter($redisConnection, '', 0, $marshaller);

Symfony 7.x 实测,同一个 100 行用户列表数据:

构造方式 输出格式 字节数
new DefaultMarshaller() PHP serialize(a:3:{...}) 9982
new DefaultMarshaller(true) igbinary(\x00\x00\x00\x02...) 2726

unmarshall 阶段是双向兼容的——通过字节第二位是不是 : 自动判断格式,既能读 PHP serialize 又能读 igbinary。所以已有缓存数据在切换 marshaller 后不需要清理,新写入的会变成 igbinary 格式。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

踩过的坑

1. 空 session 问题(Issue #146)

igbinary 把空数组 [] 序列化成 \x00\x00\x00\x02\x14\x00(非空二进制,6 字节),而 PHP 原生 session handler 把空 session 存成空字符串。切换序列化器后,空 session 检测会异常。Symfony 在 AbstractSessionHandler 里写了 workaround:把 igbinary_serialize([]) 的值缓存起来,做精确比对绕过这个问题。对应 Issue #146 由 Symfony 维护者 nicolas-grekas 在 2017 年提出,2 天后被关闭(won't fix),但 Symfony 那段 workaround 至今还在代码里。

2. PHP 8.2 dynamic properties

PHP 8.2 废弃了 dynamic properties,PHP 9.0 会变成 Error。igbinary 3.2.7 起跟进这个变化:

  • 在 readonly class(8.2+)或 readonly 属性(8.1+)上反序列化动态属性,直接抛 Error
  • 在 PHP 8.2 普通类(类没标 #[AllowDynamicProperties])上反序列化动态属性,抛 deprecation notice
  • PHP 9.0 起,上面的 deprecation 升级为 Error

如果你的缓存里有老数据,反序列化时类定义已经迁移到 readonly 或者去掉了 dynamic 属性,反序列化会失败。升级 PHP 主版本前最好清一遍相关缓存。

3. 跨 PHP 版本共享缓存数据的风险

igbinary 二进制格式本身从 v2 起就没变过(header magic \x00\x00\x00\x02),TECH_NOTES 明确支持 32/64 位和字节序可移植。但 PHP 内部对象表示会跨大版本变(比如 enum、readonly class、interned string 优化),所以跨 PHP 主版本共享 Redis 缓存有风险。生产升级 PHP 主版本时,如果 Redis 里还有老版本写的对象缓存,最好做一次 flush 或灰度。

什么场景不要用

  • 跨语言数据交换:输出的二进制只有 PHP 能读。Node.js / Python / Go 想读你的 Redis 数据?老老实实用 JSON。
  • 需要人眼可读的场景:出问题想 redis-cli get 直接看值?PHP serialize 至少还能读出个大概,igbinary 全是 \x00
  • 序列化数据需要长期归档:跨 PHP 主版本风险见上文。
  • 写多读少的场景:serialize 比 igbinary 略快(因 compact_strings 开销),如果你 90% 时间在写、10% 时间在读,提升有限。

总结

PHP 默认 serialize() 在缓存场景的浪费是隐形的,你不测不知道。igbinary 装上配个 ini,缓存体积立刻砍 70%+,反序列化快 2 倍,还跟 __sleep / __wakeup / 循环引用完全兼容。Nextcloud 默认开、Moodle 深度集成、Symfony 提供可选支持,生态稳定。只要避开 phpredis 编译选项和跨 PHP 版本这两个坑,缓存场景闭眼装就对了。

最近浏览
IP用户:43.166.*.*
18 分钟前 Mobile Safari iOS 13.2
累计浏览次数:2
评论
点击登录
phpreturn,PHP武器库,专注PHP领域的项目和资讯,收录和介绍PHP相关项目。
最近浏览 点击登录
累计浏览次数:338691
一周浏览次数:2914
今日浏览次数:144

本站所有权利归 phpreturn.com 所有

举报/反馈/投稿邮箱:phpreturn@ulthon.com

鲁ICP备19027671号-2