php字符串不可变导致拼接频繁分配内存,workerman长连接下易oom;应启用strict_mode与max_package_size、用implode/sprintf替代循环拼接、避免业务层无节制拼接。

因为 PHP 的 string 是不可变类型,每次用 . 拼接都会生成新字符串,旧字符串滞留内存直到 GC 触发;在长连接、高并发场景下,这种行为会快速累积未释放内存,最终触发 OOM 或被系统 kill。
Workerman 中字符串拼接的内存放大效应
Workerman 进程常驻内存,不重启;一次请求中若循环拼接 10 万次字符串(如日志组装、协议封包),就会产生至少 10 万个中间 string 对象。PHP 的 GC 对短生命周期对象不敏感,尤其存在引用或闭包捕获时,这些字符串可能长期卡在内存里。
- 拼接 1MB 字符串,用
.做 100 次分段追加 → 实际分配内存可能超 5MB(因每次扩容 + 副本拷贝) -
str_repeat('a', 1024*1024)生成 1MB 字符串是紧凑的;但$s = '';$s .= 'a';循环 100 万次,底层会多次 realloc 底层数组,伴随内存碎片 - 若拼接内容来自客户端(如未校验的
$connection->recv()数据),攻击者可故意发大量小片段,诱导服务端反复分配/复制
TextProtocol 场景下拼接与协议解析叠加风险
当使用 TextProtocol 且未启用 strict_mode 和 max_package_size 时,恶意客户端发一个不含换行的 200MB 数据流,Workerman 会持续把数据 append 到缓冲区——这本身就是一个巨型字符串拼接过程。此时即使业务层没写 .,协议层已替你“拼”完了。
- 默认
TextProtocol不设限,缓冲区可无限增长,直接压垮进程内存 - 开启
strict_mode => true后,一旦缓冲区超max_package_size仍无换行,立刻断连,从源头阻断拼接行为 - 务必通过
$worker->protocol_args显式传入配置,仅 new TextProtocol 类无效
替代方案:避免拼接,改用流式构造或预分配
在 Workerman 的 onMessage 或 onWorkerStart 中,能不拼就不拼;必须拼时,优先复用结构、预估长度、绕过字符串中间态。
- 协议响应尽量用
$connection->send(['status'=>200, 'data'=>$payload])→ 序列化交给json_encode()处理,它内部用更优的缓冲策略 - 需要拼接多段固定内容(如 HTML 模板),用
sprintf()或vsprintf(),比.少创建临时对象 - 处理大文本(如日志聚合),改用
file_put_contents($log_file, $line, FILE_APPEND | LOCK_EX),让 OS 管理缓冲,不囤积在 PHP 内存 - 绝对不要在
onMessage回调里做$log = ''; foreach ($items as $i) { $log .= "$i\n"; }—— 改用implode("\n", $items),它底层走 C 实现,不逐段分配
最易被忽略的一点:Workerman 的内存问题往往不是单次拼接导致的,而是“协议层未设防 + 业务层无节制拼接 + 进程不重启”三者叠加。只要其中一环堵住,就能避免雪崩。配置 max_package_size 是成本最低的防线,别跳过。











