workerman+mongodb易卡死或连接耗尽,因常驻进程下各worker独立创建连接池,导致maxpoolsize被乘以worker数而打满mongodb连接上限;正确做法是在onworkerstart中单例复用mongodb\driver\manager并配置minpoolsize、maxpoolsize等参数。

Workerman 本身不内置 MongoDB 驱动,必须搭配 PHP 的 mongodb 扩展(官方驱动)使用;直接用 mysqli 或 PDO 方式连 MongoDB 会失败。
为什么 Workerman + MongoDB 容易卡死或连接耗尽
Workerman 是常驻内存的异步进程模型,而默认的 MongoClient(旧扩展)或未正确复用的 MongoDB\Driver\Manager 实例,在多 Worker 进程下会各自创建独立连接池,导致:
- 每个 Worker 进程都维持一套连接池,
maxPoolSize被乘以 Worker 数量,迅速打满 MongoDB 的maxConnections - PHP-FPM 下常见的“短连接复用”逻辑在 Workerman 中失效,连接不会自动释放或回收
- 未设置
minPoolSize=1或maxIdleTimeMS时,空闲连接长期占用,触发 MongoDB 的连接拒绝(错误:Failed to connect to XXX:27017: Connection refused)
Workerman 中安全复用 MongoDB 连接的写法
核心原则:连接实例必须在 Worker 进程启动时初始化,并全局复用,不能在 onMessage 回调里反复 new。
- 在
Worker::onWorkerStart回调中创建MongoDB\Driver\Manager实例,并存入静态变量或全局容器 - 避免使用
MongoDB\Client(它内部会为每次操作新建 Session,高并发下开销大) - 连接字符串必须显式指定连接池参数,例如:
mongodb://localhost:27017/?minPoolSize=2&maxPoolSize=20&maxIdleTimeMS=60000 - 读操作优先走
readPreference=secondaryPreferred,写操作保持primary,避免从节点堆积读压力
示例片段:
$manager = null;
Worker::onWorkerStart = function($worker) {
global $manager;
$manager = new MongoDB\Driver\Manager(
'mongodb://localhost:27017/mydb?minPoolSize=2&maxPoolSize=20',
['typeMap' => ['root' => 'array', 'document' => 'array']]
);
};
$worker->onMessage = function($connection, $data) {
global $manager;
$bulk = new MongoDB\Driver\BulkWrite;
$bulk->insert(['ts' => time(), 'val' => $data]);
$manager->executeBulkWrite('mydb.mycoll', $bulk, ['writeConcern' => new MongoDB\Driver\WriteConcern(1)]);
};
高并发写入时 MongoDB 侧必须同步调整的配置
仅改 PHP 层不够,MongoDB 服务端若仍用默认配置,writeConcern={w:1} 在副本集下可能成为瓶颈。
- 确认存储引擎是
WiredTiger(db.serverStatus().storageEngine.name返回wiredTiger),MMAPv1 已弃用且不支持文档级锁 - 写入密集场景建议关闭 journal 确认(
j:false),但仅限日志可丢失的实时指标类数据;关键业务仍应设{w:"majority", j:true} - 副本集 oplog 大小必须足够:若每秒写入 5 万条,oplog 至少保留 30 分钟,否则 secondary 同步延迟飙升甚至中断
- 禁用过期索引的实时删除(
expireAfterSeconds),改用定时夜间批量deleteMany,避免白天 delete 和 insert 争抢锁
真正容易被忽略的点
Workerman 的 onClose 回调里不做任何 MongoDB 清理动作 —— 因为连接由 Manager 自动管理,强行 close 或 unset 可能引发段错误;唯一要盯的是 MongoDB 日志里的 connection accepted 和 connection closed 是否成对,不成对说明连接泄漏发生在 PHP 层对象生命周期之外,大概率是 Manager 实例被意外销毁或作用域错乱。











