Hyperf 3.0 的 event-dispatcher 仅支持进程内同步分发,dispatch() 不跨服务触发监听器,因事件未出当前内存空间;跨服务需手动对接消息队列(如 Redis/RabbitMQ),序列化事件并由消费者服务反序列化后本地 dispatch。

Hyperf 3.0 的 hyperf/event-dispatcher 默认只支持进程内同步事件分发,跨服务异步监听必须自行对接消息中间件(如 Redis、RabbitMQ、Kafka),框架不内置跨进程能力。
为什么 dispatch() 不会自动跨服务触发监听器
调用 $eventDispatcher->dispatch(new UserRegistered($id)) 时,dispatch() 方法仅在当前进程内遍历已注册的监听器并同步执行。即使监听器类存在、注解正确、命名空间无误,其他 worker 进程或独立服务里的监听器完全收不到该事件——因为事件对象没出过当前内存空间。
- Hyperf 的事件总线是纯内存级的,基于 PSR-14 实现,和 Laravel 的
Event::dispatch()行为一致 -
#[Listener]注解只影响当前应用实例的自动注册,不产生网络通信或序列化广播 - 多 Worker 模式下,每个进程持有独立的监听器列表,彼此隔离
跨服务必须手动桥接消息队列
要让「用户注册」这个事件被另一个部署在不同机器上的「积分服务」消费,得把事件对象序列化后发到共享消息通道,再由目标服务拉取、反序列化、手动调用 dispatch()。
- 推荐使用
hyperf/amqp或hyperf/redis+hyperf/async-queue构建桥接层 - 不要在监听器里直接发消息(易造成循环或耦合),应在业务代码中显式投递
- 典型做法:在注册成功后,除了本地 dispatch,额外调用
$producer->push(...)把事件数据推入队列 - 消费者服务需单独写一个
Command或Consumer,从队列取出后构造事件对象再调用$eventDispatcher->dispatch($event)
容易忽略的序列化与类型安全问题
跨服务传输时,事件类不能依赖闭包、资源句柄、未声明的私有属性或容器实例——这些无法被 serialize() 正确处理。
- 事件类建议声明为
final,所有字段public,构造函数参数类型明确(如int $userId) - 避免在事件类中调用
$this->container或访问Context—— 消费端上下文完全不同 - 如果事件携带模型实例(如
User $user),必须转成数组或 DTO,否则反序列化失败或引发 autoload 错误 - 消费者端需确保事件类定义与生产者完全一致(包括命名空间、文件路径、PHP 版本兼容性)
别指望 config/autoload/listeners.php 能跨服务生效
这个配置文件只用于手动注册监听器,且只在当前应用启动时加载一次。它不会被序列化、不会随事件一起发送,更不会在远程服务中自动生效。
- 如果你看到文档里提到这个文件,那只是针对单体多模块场景的补充注册方式,不是跨服务方案
- 跨服务场景下,监听逻辑应放在消费者服务自己的
app/Listener/下,并通过队列 Consumer 触发,而非靠 event-dispatcher 自动发现 - 切勿尝试把整个监听器类打包发过去——监听器含容器依赖、生命周期钩子,根本无法远程实例化
真正跨服务的事件流,本质是「业务事件 → 消息队列 → 独立消费者 → 本地 event-dispatcher」,中间那根消息链路必须自己搭,没有捷径。任何试图绕过消息中间件、仅靠改配置或加注解就想实现跨服务监听的做法,都会卡在进程隔离这道墙上。


















