学完这篇,你能判断手上那个「一执行就超时」的 PHP 任务该用 Swoole Task 还是消息队列,并且知道两种方案的落地写法和容易出问题的地方。
第一步:先给任务分档
并不是所有标着「异步」的需求都值得引入 Swoole。先用三个问题把任务分成三档:
- 频率低、对延迟容忍度高(每日统计、清理回收站、生成站点地图)→ 计划任务就够,不要额外引入其他组件。
- 请求内触发、只想尽快返回响应(发邮件、生成缩略图、写操作日志)→ 用进程内异步队列。
- 需要跨机器处理、需要削峰、需要能重放(批量推送、大批量导入、订单类流水)→ 独立的消消息队列。
如果分档判断错了,代价很明显:用 Swoole Task 做削峰,进程一重启队列就会全部丢失;用 Kafka 发通知邮件,多维护一套中间件,出问题时还难以排查。
第二步:Swoole Task 的写法与边界
在 Server 上开启 task 进程,请求里投递,task 回调里干活:
$server = new Swoole\Http\Server('0.0.0.0', 9501);
$server->set([
'worker_num' => 4,
'task_worker_num' => 2, // 一般取 worker_num 的 1/4 ~ 1/2
'task_max_request' => 2000, // 防长驻进程内存泄漏
'max_wait_time' => 5,
]);
$server->on('request', function ($req, $res) use ($server) {
$taskId = $server->task(['type' => 'mail', 'to' => 'a@b.com']);
$res->end('queued:' . $taskId);
});
$server->on('task', function ($server, $taskId, $workerId, $data) {
// 真正的耗时逻辑放这里
return 'done';
});
$server->on('finish', function ($server, $taskId, $data) {
// 可选:回收结果
});
注意:Swoole Task 本质是本机内存队列,进程重启或机器宕机,任务直接消失。凡是「丢了要赔钱」的任务,都不能只靠它。
注意:task() 默认会阻塞,直到投递成功;taskwait 会阻塞等待结果。在 worker 里高频投递时,队列满了会拖慢响应,量大要加限流或改走外部队列。
第三步:消息队列的选择
从轻到重三档:
- Redis List(
LPUSH + BRPOP):零新增组件,几行代码就能用,单机上万 QPS 没压力。缺点是没有 ACK,消费过程中崩溃会丢消息,需要自己写「处理中列表」来补偿。
- Redis Stream(
XADD / XREADGROUP / XACK):有消费组、有 ACK、有 pending 重投,Redis 5+ 自带。绝大多数社区类业务用它完全够。
- RabbitMQ / Kafka:需要跨语言、需要复杂路由、需要长时间堆积后回放时再考虑,运维成本是真实存在的。
Stream 上手三条命令:
redis-cli XADD jobs '*' type mail to a@b.com
redis-cli XGROUP CREATE jobs g1 0 MKSTREAM
redis-cli XREADGROUP GROUP g1 c1 COUNT 10 BLOCK 5000 STREAMS jobs '>'
redis-cli XACK jobs g1 <id>
第四步:先用现成的计划任务体系
如果跑的是 Clara BBS 这类自带计划任务体系的系统,轻量异步不必额外搭建:用 Cron::register 统一注册任务,前台访问时懒触发(装完即可用),也可以配系统 crontab 每分钟调 /cron 精准触发;内部有文件锁防并发重跑,运行结果写入数据库可查。再配合后台「系统工具 → 缓存设置」开启 Redis,榜单、热点这类数据统一走 Cache 层,Redis 异常会自动降级到文件驱动。
注意:不论选哪种方案,任务都必须幂等。重试、超时重投、人工补跑都会导致重复执行,写库前先用业务唯一键判重。
第五步:给出结论
- 单机、对延迟敏感、失败后能重算 → Swoole Task。
- 单机或小集群、要求不丢 → Redis Stream。
- 多机多语言、削峰填谷 → RabbitMQ / Kafka。
再补一条通用建议:任何方案都要监控队列积压长度。任务失败可以重试,积压持续上涨说明容量已经不够了,比报错更危险。
小结
- 先按「频率 / 延迟要求 / 是否可丢」把任务分三档,再选择方案。
- Swoole Task 快、简单,但队列在内存里,重启即丢,只适合可重算的任务。
- 要求不丢又要轻量,优先使用 Redis Stream,不要一开始就上 Kafka。
- 有现成的计划任务体系(
Cron::register + /cron)就先复用它,它自带文件锁和运行记录。
- 幂等和积压监控,比选哪个组件更重要。