Hyperf 的 Subscriber 组件不支持 Redis Pub/Sub,因其基于框架事件分发器,而 Redis 订阅需独立协程+phpredis长连接,混用会导致阻塞、连接冲突或 Worker 僵死。

Hyperf 的 Subscriber 组件本身不支持 Redis 发布订阅(Pub/Sub)——它专用于 Hyperf 内部事件系统(如 AfterWorkerStart、BootApplication 等),和 Redis 协议无关。想用 Redis 实现发布订阅,必须绕过 Subscriber,直接操作 Redis 连接。
为什么不能用 Subscriber 组件做 Redis Pub/Sub
Hyperf 的 Subscriber 是基于 PHP 的事件分发器(Hyperf\Event\EventDispatcher),监听的是框架生命周期或自定义事件,不是 Redis 的 SUBSCRIBE 命令。试图在 Subscriber 里调用 $redis->subscribe() 会导致进程阻塞,破坏协程调度,甚至让 Worker 卡死。
-
Subscriber的方法是同步、短时执行的,而 RedisSUBSCRIBE是长连接 + 阻塞式等待消息 - Hyperf 默认 Redis 客户端(
Hyperf\Redis\Redis)底层用的是predis/predis或phpredis,都不支持在协程中安全地复用阻塞连接 - 即使强行调用
subscribe(),也会抛出RedisException: read error on connection或直接超时断连
正确做法:用 Hyperf\Contract\StdoutLoggerInterface + 独立协程启动 Redis::pubSubLoop()
Hyperf 提供了 Hyperf\Redis\RedisFactory 和原生 Redis 扩展支持的 pubSubLoop 方法(需启用 phpredis),这是唯一稳定可行的方式。关键点是:必须在 Worker 启动后、用独立协程运行,且不能共享主连接。
- 确保
phpredis已安装并启用(extension=redis.so),predis不支持pubSubLoop - 配置 Redis 连接时,为 Pub/Sub 单独配一个连接池(避免和业务读写争抢)
- 在
AfterWorkerStart事件中启动协程,而非Subscriber类里写逻辑 - 示例代码片段:
// app/Listener/AfterWorkerStartListener.php
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\AfterWorkerStart;
use Hyperf\Redis\RedisFactory;
use Psr\Container\ContainerInterface;
#[Listener]
class AfterWorkerStartListener implements ListenerInterface
{
public function __construct(private ContainerInterface $container) {}
public function listen(): array
{
return [AfterWorkerStart::class];
}
public function process(object $event): void
{
// 启动一个独立协程处理 Redis 订阅
go(function () {
$redis = $this->container->get(RedisFactory::class)->get('pubsub');
// 注意:这里必须用 phpredis 原生实例
$redisInstance = $redis->getRedis();
if (!$redisInstance instanceof \Redis) {
throw new RuntimeException('pubsub pool must use phpredis');
}
$redisInstance->subscribe(['channel:log', 'channel:notify'], function ($redis, $channel, $message) {
var_dump("Received on {$channel}: {$message}");
// 注意:此处不能调用协程函数(如 go(), co::sleep),会报错
// 如需异步处理,应投递到 task 或 channel 中
});
});
}
}
常见坑:消息处理中混用协程、连接复用、频道名不一致
实际部署时,80% 的失败都来自这三个点:
- 在
subscribe的回调里直接调用go()或co::sleep()——phpredis的回调运行在同步上下文,会触发Fatal error: Swoole\Coroutine::create(): must be called in the coroutine - 复用业务 Redis 连接池(如
default)——SUBSCRIBE后该连接进入特殊状态,不能再执行GET/SET,否则报Redis server went away - 发布端用
PUBLISH channel:log "msg",订阅端却监听log或channel.log—— Redis 频道名是纯字符串匹配,不支持通配符(除非用PSUBSCRIBE) - 没设置
timeout导致连接空闲断开:可在redis.php配置中为 pubsub 连接池加'timeout' => 0(禁用超时)
真正难点不在怎么写几行订阅代码,而在于守住「订阅连接独占」「回调零协程」「频道名严格一致」这三条线。一旦越界,表现就是收不到消息、Worker 偶发僵死、日志里反复出现 Connection lost —— 这些都不是配置问题,是模型误用。


















