PHP 异步怎么实现?Swoole、Workerman 使用场景对比
引言
传统 PHP-FPM 是"请求进来→执行脚本→退出进程"的同步模型,天然不适合长连接、实时推送、高并发 IO 编排。
但现代 PHP 已经能很好地做异步:消息队列、常驻进程、事件驱动、协程,四条路各有适用边界。
本文系统讲清楚 PHP 异步的几种实现方式,并重点对比 Swoole 与 Workerman 的底层差异、选型逻辑和落地代码。
目录
- PHP 为什么"看起来不支持异步"
- PHP 异步的 4 类主流方案
- 消息队列 + Worker(最稳的生产方案)
- Swoole:协程异步(高性能 IO 密集)
- Workerman:纯 PHP 事件驱动(长连接利器)
- Swoole vs Workerman 核心对比
- 场景化选型决策树
- 常驻内存服务的 6 个避坑点
一、PHP 为什么"看起来不支持异步"
- PHP 语言本身没有 Node.js 那种原生事件循环
- FPM 模式下:一个请求一个进程,执行完就销毁
- 阻塞调用(
curl_exec、sleep、同步 PDO)会卡住整个进程 - "异步"不是语法特性,而是运行模型 + IO 调度问题
所以 PHP 做异步,本质是换运行模型:
FPM(同步) → CLI 常驻(事件/协程) → 队列解耦(异步任务)
二、PHP 异步的 4 类主流方案
| 方案 | 异步能力 | 可靠性 | 适用场景 |
|---|---|---|---|
| 消息队列 + Worker | 任务异步 | 高 | 业务系统首选 |
| Swoole 协程 | IO 并发 | 中高 | 高并发接口、网关 |
| Workerman 事件驱动 | 长连接 | 中高 | WebSocket、IM、物联网 |
| pcntl/Cron/脚本 | 简单后台任务 | 低中 | 导出、统计、补数据 |
三、消息队列 + Worker(最稳的生产方案)
1. 流程
FPM 请求
└─> 投递任务到 Redis/RabbitMQ
└─> 独立 CLI Worker 消费执行
└─> 发邮件 / 推消息 / 写日志 / 调第三方
2. 投递任务(FPM 侧)
// 用 Redis 简单队列
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$task = [
'type' => 'send_email',
'to' => 'user@example.com',
'data' => ['order_id' => 1001],
];
$redis->rPush('queue:email', json_encode($task));
3. Worker 消费(CLI 常驻)
while (true) {
$raw = $redis->blPop('queue:email', 5);
if (!$raw) continue;
$task = json_decode($raw[1], true);
try {
sendEmail($task['to'], $task['data']);
markDone($task);
} catch (Throwable $e) {
// 重试 / 死信队列
$redis->rPush('queue:email:failed', $raw[1]);
}
}
用 Supervisor 保活:
[program:email-worker]
command=php /app/worker/email.php
autostart=true
autorestart=true
numprocs=4
适合:90% 的业务系统(Laravel/ThinkPHP 直接上队列,别先想 Swoole)
四、Swoole:协程异步(高性能 IO 密集)
核心模型
- C 扩展,底层 epoll/kqueue
- Reactor 线程 + Worker 进程 + 协程调度
- 协程内遇到 IO 自动挂起,不阻塞进程
HTTP 并发调用示例
Swoole\Coroutine\run(function () {
$client = new Swoole\Coroutine\Http\Client('api.example.com', 80);
$client->set(['timeout' => 2]);
$client->get('/user/1');
$user = $client->getBody();
$client->get('/order/1001');
$order = $client->getBody();
// 同步写法,并发执行
var_dump($user, $order);
});
WebSocket 服务
$server = new Swoole\WebSocket\Server('0.0.0.0', 9501);
$server->on('open', function ($server, $req) {
echo "client {$req->fd} connected\n";
});
$server->on('message', function ($server, $frame) {
// 协程内查库、推消息
$server->push($frame->fd, 'recv: ' . $frame->data);
});
$server->on('close', function ($server, $fd) {
echo "client {$fd} closed\n";
});
$server->start();
适合
- 高 QPS API 网关
- 微服务(Hyperf)
- 实时风控、订单路由
- 大量并发 HTTP/Redis/MySQL 调用
五、Workerman:纯 PHP 事件驱动(长连接利器)
核心模型
- 纯 PHP(pcntl + stream_select / epoll)
- 多进程 + 事件回调
- 无原生协程(Webman 2.x 可用 Fiber 模拟)
TCP/WebSocket 示例
use Workerman\Worker;
$ws = new Worker('websocket://0.0.0.0:2345');
$ws->onConnect = function ($conn) {
echo "new connection\n";
};
$ws->onMessage = function ($conn, $data) {
// 注意:这里不能写 file_get_contents 阻塞调用
$conn->send('hello: ' . $data);
};
$ws->onClose = function ($conn) {
echo "close\n";
};
Worker::runAll();
启动:
php start.php start -d
适合
- 聊天室、客服系统、推送
- 物联网设备心跳(MQTT/Modbus)
- 不想装 C 扩展、运维受限环境
- 中小并发长连接(几万级很稳)
六、Swoole vs Workerman 核心对比
| 维度 | Swoole | Workerman |
|---|---|---|
| 底层 | C 扩展 | 纯 PHP |
| 并发模型 | 协程 + Reactor | 多进程事件驱动 |
| 异步写法 | 同步风格协程 | 回调/事件 |
| 性能 | 更高(IO 密集) | 高(够用) |
| 长连接 | 强 | 强且易调试 |
| 部署 | 需编译扩展 | Composer 引入 |
| 调试 | 难(协程上下文) | 容易(Xdebug 友好) |
| 学习曲线 | 陡 | 平缓 |
| 上层框架 | Hyperf / EasySwoole | Webman / GatewayWorker |
| 内存占用 | 更低 | 略高 |
七、场景化选型决策树
1. 普通业务系统(后台/CMS/电商后台)
FPM + 框架 + Redis 队列
- 别上 Swoole
- 导出、通知、统计全丢队列
- 稳定、好调试、改代码即时生效
2. 需要 WebSocket / IM / 推送
- 能装扩展、要性能 → Swoole
- 不能装扩展、快速交付 → Workerman / Webman
3. 高并发 API / 微服务
- IO 密集、多服务聚合 → Swoole + Hyperf
- 中等并发、要简单 → Webman
4. 物联网 / 私有协议
- 设备接入、TCP 解析 → Workerman(协议灵活)
- 超大规模接入 → Swoole
5. 混合架构(最推荐)
Nginx
├─ /admin → FPM(Laravel/ThinkPHP)
├─ /api → Webman / Hyperf
├─ /ws → Swoole/Workerman WebSocket
└─ 队列消费 → CLI Worker(Supervisor)
八、常驻内存服务的 6 个避坑点
1. 全局/静态变量跨请求污染
// 错误
class UserService {
public static $uid;
}
// 正确:用请求上下文或协程上下文
$context = Swoole\Coroutine::getContext();
$context['uid'] = 1001;
2. 连接不能写全局
// 错误:启动一次,请求间复用会串
$db = new PDO(...);
// 正确:连接池 / 协程内获取
$pool->getConnection()->query(...)
3. 阻塞函数会卡死
sleep()→ 用Co::sleep()file_get_contents→ 用协程 HTTP Client- 同步 PDO → 用 Swoole MySQL 协程客户端
4. 代码热更新
- FPM:改了就生效
- Swoole/Workerman:
reload或restart - Supervisor 必须前台运行,不能
-d再托管
5. 心跳与僵尸连接
WebSocket 必须:
- 客户端发 ping
- 服务端检测
lastMessageTime - 超时断开,释放 fd
6. 日志与监控
- 业务日志走 Monolog
- 慢请求:Swoole slowlog / Workerman 自定义计时
- 进程数:CPU 核数 × 2~4
- 系统参数:
ulimit -n、tcp_tw_reuse
九、一句话总结
- 业务异步:先上「消息队列 + Worker」,别滥用常驻服务
- 高并发 IO:选 Swoole(协程、性能天花板)
- 长连接/快速交付/不能装扩展:选 Workerman
- 真实生产:FPM + 队列 + Webman/Swoole 混合部署最稳
PHP 异步不是"二选一",而是按业务拆层:
同步请求走 FPM,耗时任务走队列,实时通道走 Swoole/Workerman。