为什么面试官爱问 Workerman?
在 PHP 中高级岗位面试中,ThinkPHP + Workerman 的组合几乎是绕不开的话题。原因很直接:
- 传统 PHP-FPM 模式无法常驻内存,每次请求都要重新加载框架,性能瓶颈明显;
- 实时推送、IM、物联网、游戏服务端等场景需要长连接支持;
- Workerman 是纯 PHP 编写的高性能 Socket 服务器,与 ThinkPHP 生态结合后能快速落地项目。
面试官问这道题,核心想考察三点:你是否理解常驻内存模型的差异、能否独立完成集成、是否踩过并解决过实际生产问题。
一、Workerman 与 ThinkPHP 的集成方式
1.1 安装与基础结构
composer require workerman/workerman
推荐在 ThinkPHP 项目根目录创建独立目录,例如 server/,与 Web 应用解耦:
project/
├── app/ # ThinkPHP 应用
├── server/
│ ├── start.php # Workerman 启动入口
│ └── handler/ # 业务处理类
├── config/
└── public/
1.2 启动入口示例
// server/start.php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/../vendor/autoload.php';
// 初始化 ThinkPHP 应用(关键步骤)
$app = new \think\App();
$app->initialize();
$worker = new Worker('websocket://0.0.0.0:8282');
$worker->count = 4; // 根据 CPU 核数设置
$worker->onConnect = function (TcpConnection $connection) {
echo "新连接: {$connection->id}\n";
};
$worker->onMessage = function (TcpConnection $connection, $data) {
// 可调用 ThinkPHP 的模型、缓存、验证器等
$result = \app\common\service\MessageService::handle($data);
$connection->send(json_encode($result));
};
$worker->onClose = function (TcpConnection $connection) {
echo "连接断开: {$connection->id}\n";
};
Worker::runAll();
启动命令:
php server/start.php start -d
1.3 面试高频追问:ThinkPHP 在 Workerman 中如何初始化?
这是集成中最容易出问题的地方。必须手动调用 $app->initialize(),否则容器、配置、路由都不会加载。但要注意:
- 不要重复初始化,否则会报「常量已定义」等错误;
- 多进程模式下,每个 Worker 进程独立初始化一次;
- 数据库连接在常驻内存中会「假死」,需要配置断线重连。
二、长连接服务的核心设计
2.1 连接鉴权
WebSocket 握手阶段无法直接携带 Header 以外的复杂参数,常见做法是:
$worker->onConnect = function (TcpConnection $connection) {
$connection->authTimer = \Workerman\Timer::add(5, function () use ($connection) {
if (!isset($connection->uid)) {
$connection->close();
}
}, [], false);
};
$worker->onMessage = function (TcpConnection $connection, $data) {
$msg = json_decode($data, true);
if ($msg['type'] === 'auth') {
$uid = AuthService::verify($msg['token']);
if ($uid) {
$connection->uid = $uid;
\Workerman\Timer::del($connection->authTimer);
// 绑定 uid 与 connection
Gateway::bindUid($connection->id, $uid);
}
}
};
面试要点:连接建立后必须设置鉴权超时定时器,防止未认证连接长期占用资源。
2.2 心跳机制
长连接必须有心跳,否则 NAT 超时、移动网络切换都会导致「死连接」:
// 服务端定时检测
\Workerman\Timer::add(30, function () use ($worker) {
foreach ($worker->connections as $connection) {
if (time() - $connection->lastMessageTime > 90) {
$connection->close();
}
}
});
客户端一般每 30 秒发送一次 ping,服务端回复 pong 并更新 lastMessageTime。
2.3 消息推送与广播
单机场景下可以遍历 $worker->connections,但多进程、多机部署必须借助 Channel 组件 或 Redis 发布订阅:
// 使用 Redis 跨进程推送
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$redis->subscribe(['push_channel'], function ($redis, $channel, $message) {
$data = json_decode($message, true);
foreach ($worker->connections as $connection) {
if ($connection->uid == $data['to_uid']) {
$connection->send($data['content']);
}
}
});
三、生产环境必须解决的五个坑
- 内存泄漏:常驻进程下全局变量、静态属性只增不减。建议定期重启(
max_request或 crontab),并使用memory_get_usage监控。 - 数据库断连:MySQL 默认 8 小时空闲断开,需在
onWorkerStart中设置定时重连或使用连接池。 - 日志与调试:Workerman 下
dump()、halt()不可用,日志必须写入文件,推荐 monolog 或 ThinkPHP 的 Log 门面。 - 平滑重启:使用
php start.php reload而非restart,避免中断现有连接。 - 进程数设置:
count建议设为 CPU 核数的 1~2 倍,过多会导致上下文切换开销。
四、面试答题思路总结
当被问到「ThinkPHP 如何集成 Workerman」时,建议按以下结构回答:
- 先讲差异:FPM 是「请求-销毁」模型,Workerman 是「常驻内存」模型,因此框架初始化、DB 连接、全局状态都要重新设计;
- 再讲集成:composer 安装 → 独立启动入口 → 手动
initialize()→ 业务类复用 ThinkPHP 组件; - 重点讲长连接:鉴权、心跳、断线重连、跨进程推送四大件;
- 最后讲生产:内存泄漏、平滑重启、进程数、日志监控。
能把这四层讲清楚,基本就能拿到这道题的满分。如果还能结合自己项目中「消息延迟从 500ms 优化到 50ms」之类的具体数据,面试官会立刻对你刮目相看。
结语
Workerman 不是银弹,它适合长连接、高并发推送场景,但也会带来常驻内存的复杂性。ThinkPHP 与它的集成,本质上是把框架能力从「每次请求重建」迁移到「进程启动时构建一次」。理解这个本质,面试和实战都不会慌。
未经允许不得转载:任鹏个人博客 » ThinkPHP 面试精讲:Workerman 集成与长连接服务

