PHP 默认的 serialize() 在缓存场景下其实是个隐形浪费——把一个 100 行的数据库 rowset 拍成字符串,光重复的 key 名(id、name、email)就占掉一半体积。我跑了一组真实数据:同一个用户列表,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 作为可选序列化器。

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。

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 倍

测试数据是 100 个用户记录,每个记录有 6 个字段加一个 profile 子对象——典型的数据库 rowset 结构。
体积对比
| 序列化方式 | 体积 | 相对 PHP serialize |
|---|---|---|
| PHP serialize | 32866 B | 基线 |
| igbinary | 8946 B | -73% |
| json_encode | 24069 B | -27% |
igbinary 的体积优势主要来自两点:
-
重复字符串去重(
compact_strings默认开启):100 条记录里重复出现的id、name、email、role这些 key,以及重复的值(比如member、active、默认头像 URL),只在二进制里存一份,后续用引用索引。 -
二进制编码而非文本:整数、布尔、字符串长度用紧凑的二进制表示,不再写
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 的节省远大于序列化本身。
二进制格式可视化
光看字节数太抽象,直接看一段二进制。

把 ['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+ 才稳定支持。
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 格式。
踩过的坑
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直接看值?PHPserialize至少还能读出个大概,igbinary 全是\x00。 - 序列化数据需要长期归档:跨 PHP 主版本风险见上文。
- 写多读少的场景:serialize 比 igbinary 略快(因 compact_strings 开销),如果你 90% 时间在写、10% 时间在读,提升有限。
总结
PHP 默认 serialize() 在缓存场景的浪费是隐形的,你不测不知道。igbinary 装上配个 ini,缓存体积立刻砍 70%+,反序列化快 2 倍,还跟 __sleep / __wakeup / 循环引用完全兼容。Nextcloud 默认开、Moodle 深度集成、Symfony 提供可选支持,生态稳定。只要避开 phpredis 编译选项和跨 PHP 版本这两个坑,缓存场景闭眼装就对了。
原文标题: [igbinary/igbinary]缓存体积砍一半,反序列化快一倍的隐形加速器
原文地址: https://phpreturn.com/index/a6a38ae6232621.html
原文平台: PHP武器库
版权声明: 本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。