webman 集成 pulsar 必须直连官方 php sdk,不可套用 spring boot 方案;需分离 consumer worker 与 websocket 主循环,配置合理分片、ack 超时与重试策略,并严格遵循租户/命名空间隔离及进程退出时手动 redeliver。

Webman 本身不原生支持 Pulsar,集成必须绕过框架封装、直连 Pulsar Client SDK,否则在高并发长连接场景下会因阻塞调用、协程不兼容或连接复用失控导致消息积压、ACK 超时甚至进程卡死。
为什么不能直接用 Spring Boot 那套 Pulsar Starter
Webman 是基于 Swoole 的 PHP 框架,而 spring-boot-starter-pulsar 是 Java 生态的 Spring Boot 组件,完全无法在 PHP 进程中加载或运行。试图“移植配置”或“模仿 YAML 写法”只会浪费时间——PHP 没有 Bean 容器、没有自动装配、也没有 PulsarTemplate 这类抽象层。
- 所有 Pulsar 客户端操作必须调用官方 PHP SDK(如
pulsar-php-client)或通过ext-pcntl/ext-sockets自行封装 TCP 连接 - Webman 的协程模型与 Pulsar Java Client 的线程模型不兼容,强行 fork 或 exec java 命令会破坏事件循环,且性能损耗极大
- 官方 PHP SDK 功能有限:不支持事务消息、不支持 Key_Shared 订阅、延迟消息需手动计算
deliverAtTime时间戳,且无重试兜底逻辑
消费者必须用独立 Worker 进程,不能混在 WebSocket 主循环里
在 onMessage 回调里同步调用 $consumer->receive() 或轮询消费,等于把 Pulsar 拉进 Webman 的主协程调度,一旦某条消息处理慢(比如 DB 写入卡顿),整个 Worker 的所有 WebSocket 连接都会被拖住,出现大面积 Connection reset by peer。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 正确做法是启动专用 Consumer Worker:在
start.php中新增一个Worker实例,只负责从 Pulsar 拉取消息、解析、投递到本地队列(如 Redis List 或内存 RingBuffer) - WebSocket 主 Worker 通过
Redis::blpop()或eventfd异步接收已就绪消息,避免阻塞 - Consumer Worker 的
worker_num不宜设为 1:单进程消费扛不住百万级 QPS,应按 Topic 分片,每个分片绑定独立 Consumer 实例 + 独立订阅名 - 务必设置
ackTimeoutMs(建议 30000),并配合negativeAckRedeliveryDelayMs控制失败重试节奏,否则超时消息会无限堆积在未确认队列
生产者发消息必须异步+失败重试,不能依赖 sendAsync 就完事
pulsar-php-client 的 sendAsync() 只是把请求丢进 socket buffer,并不保证送达;网络抖动、Broker 拒绝、Topic 不存在等错误不会抛出异常,而是静默失败。
- 必须监听回调函数中的
$exception参数,对TimeoutException、ProducerBusyException、TopicTerminatedException做分类重试 - 重试不能简单
sleep(1)后再发:要退避累加(如第 1 次 100ms,第 2 次 300ms,第 3 次 700ms),避免打爆 Broker - 关键业务消息(如支付结果通知)必须落地本地表(
pulsar_outbox),状态为pending;发送成功后更新为sent;失败则由定时任务捞出重发,并记录失败原因字段供告警 - 批量发送时禁用
enableBatching = true:PHP SDK 的 batching 实现不稳定,容易触发BatchBuilder内存溢出,尤其当消息体大小差异大时
Topic 和租户隔离必须靠命名空间,不能靠 PHP 变量模拟
有人试图用 $topicMap['order'] = 'persistent://tenant/namespace/order-topic' 在代码里硬编码 Topic 路径,结果上线后多租户消息混发、权限错乱、监控指标全乱。
- Pulsar 的租户(tenant)、命名空间(namespace)、Topic 是强隔离层级,必须在服务端创建好,PHP 侧只读取配置,不可动态拼接
- 不同业务线必须分配独立
tenant(如pay、im、log),每个tenant下建专属namespace(如prod、test) - Webman 应从环境变量或配置中心读取完整 Topic 全路径(如
persistent://pay/prod/order-created),禁止字符串拼接 - Consumer 创建时必须显式指定
subscriptionName,且名称含租户前缀(如pay-order-consumer-v1),避免跨租户重复消费
最易被忽略的是 Pulsar 的 ACK 语义和 Webman 生命周期不匹配:Worker 进程重启时,未 ACK 的消息会重回队列,但 PHP 进程没有类似 Java 的 close() 钩子自动触发 redeliverUnacknowledgedMessages()。必须在 onWorkerStop 里主动调用 $consumer->redeliverUnacknowledgedMessages(),否则重启瞬间会丢失最多 1 分钟的消息进度。










