PHP Redis subscribe() 不能直接用于 Swoole 协程,因其底层为同步阻塞 socket recv(),协程调度器无法接管,导致整个协程挂起停摆;唯一可靠方案是用 Swoole\Process 启常驻子进程承载阻塞订阅,并通过进程通信转发消息。

PHP Redis subscribe() 为什么不能直接用在 Swoole 协程里
因为 subscribe() 是同步阻塞调用,底层用的是阻塞式 socket recv(),Swoole 协程调度器完全接管不了它。你在 onRequest 或 onMessage 里一调就卡死,整个协程挂起,后续所有请求都排队等它——不是慢,是彻底停摆。
这不是配置问题,也不是版本问题,是 PHP Redis 扩展设计决定的:它没提供非阻塞订阅接口。Swoole 自带的 Swoole\Coroutine\Redis 也明确不支持 subscribe(),文档里写死了“未实现”。
- 别试
setOption(Redis::OPT_READ_TIMEOUT, 0)或-1—— 这只影响读超时,不解除阻塞本质 - 别指望
pcntl_fork()+subscribe()在主线程里跑——fork 出的子进程仍会阻塞父进程调度 -
rawCommand('subscribe', ...)同样无效,底层还是走同一套阻塞 recv
必须用独立子进程承载 subscribe()
唯一可靠的做法:用 Swoole\Process 启一个常驻子进程,里面用原生 Redis 实例做阻塞订阅,收到消息后通过进程通信(如 $process->write()、Swoole\Coroutine\Channel)转给主协程处理。
这个子进程要自己管好连接生命周期:
立即学习“PHP免费学习笔记(深入)”;
- 每次循环前手动
$redis->connect(),别依赖pconnect()—— 断连后pconnect不会自动重连 - 必须加
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1),否则默认 5 秒超时就会退出订阅循环 - 用
try/catch RedisException捕获断连,sleep(1)后重试,避免日志刷屏 - 子进程里调用
set_time_limit(0),防止 PHP 脚本超时中断
示例关键片段:
$process = new Swoole\Process(function (Swoole\Process $proc) {
$redis = new Redis();
while (true) {
try {
$redis->connect('127.0.0.1', 6379);
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
$redis->subscribe(['task'], function ($redis, $chan, $msg) use ($proc) {
$proc->write("{$chan}:{$msg}\n");
});
} catch (RedisException $e) {
sleep(1);
}
}
});
$process->start();
publish() 可以放心在 HTTP 请求里调用
publish() 是纯发包操作,毫秒级完成,不阻塞、不等待响应,也不需要长连接。你可以在 onRequest 里直接用,甚至复用同一个 Redis 实例(只要没在做 subscribe)。
- 推荐用短连接:每次
new Redis()→connect()→publish()→close(),干净利落 - 如果用连接池或长连接,确保该连接没被用于任何
subscribe场景,否则可能出状态混乱 - 发布失败(比如 Redis 宕机)会抛
RedisException,需捕获并记录,但不影响当前 HTTP 响应流程
消息体格式和退订逻辑容易被忽略
Redis Pub/Sub 本身不校验消息格式,也不保证顺序和持久化。你得自己约定结构,比如统一用 JSON 并带 type 字段:
{"type":"order_paid","data":{"order_id":123,"amount":99.9}}
另外,subscribe() 是单次调用、无限循环的,没有内置退出机制。想优雅停止,得靠信号或文件锁:
- 子进程里监听
SIGUSR1,收到后跳出while(true)循环 - 主进程用
$process->kill(SIGUSR1)触发退订 - 别依赖
unsubscribe()—— 它只是通知 Redis 退订,但阻塞循环还在跑,实际连接未必断开
真正麻烦的从来不是怎么发、怎么收,而是连接断了谁来重连,消息丢了有没有补偿,以及——没人告诉你 subscribe() 一旦开始,就再也没法在同一个连接上干别的事了。



















