Workerman跨机房容灾需依赖外部组件实现,核心是“连接层分流+状态层共享+通信层解耦”:负载均衡须支持长连接会话保持或彻底无状态;Redis必须部署为跨机房高可用集群(如Redis Cluster),禁用单点写入;跨机房消息同步需结合Pub/Sub与AMQP/Kafka中继,并辅以本地队列兜底;心跳与故障剔除须独立运行,且全链路需统一NTP校时。

Workerman本身不提供跨机房容灾能力,必须靠外部组件组合实现;核心是“连接层分流 + 状态层共享 + 通信层解耦”,三者缺一不可。
负载均衡器必须支持长连接会话保持或彻底无状态
多机房场景下,客户端连接若被随机分发到不同机房的Workerman实例,会导致消息丢失、状态错乱。常见错误是直接用LVS或Nginx做四层转发却不配置ip_hash或sticky,结果用户反复重连、消息收不到。
- 如果业务允许无状态(如纯广播推送),可关闭会话保持,但所有用户状态必须存
Redis,且每个Workerman实例在onConnect时从Redis加载用户信息 - 如果业务强依赖会话(如在线游戏房间),必须启用会话保持,并配合DNS级故障转移——比如用
dnspod或cloudflare做健康探测+自动切流 - Nginx作为七层负载均衡时,
upstream块中要加sticky cookie=workerman_sid expires=1h domain=.example.com,否则WebSocket升级后连接会被重定向中断
Redis必须部署为跨机房高可用集群,且禁用单点写入
所有Workerman实例共用一个Redis集群来存连接映射、用户在线状态、离线消息,一旦Redis单点宕机,整个长连接系统就不可用。典型坑是只用主从复制却没开sentinel或cluster模式,主库挂了从库不会自动升主。
- 推荐用
Redis Cluster,至少3主3从,分布在两个机房(如A机房2主1从,B机房1主2从),通过redis-cli --cluster初始化 - Workerman代码里不能写死
127.0.0.1:6379,必须用tcp://redis-cluster.example.com:6379这类域名,并确保PHP的redis扩展支持集群(>=5.3.2) - 所有
setex操作必须带过期时间,避免因某台Workerman异常退出导致online:uid键永久残留
跨机房消息同步必须用Pub/Sub + 本地队列兜底
用户A在机房1发消息,用户B在机房2在线,仅靠共享Redis无法实时触发B所在Workerman进程的推送——因为Redis Pub/Sub不跨集群,且没有持久化。常见错误是只用$redis->publish(),结果B收不到。
- 必须在每个机房部署一套
Redis Pub/Sub通道,再用workerman/async-redis的subscribe监听本地通道,同时用AMQP(如RabbitMQ镜像队列)或Kafka做跨机房消息中继 - 关键逻辑:机房1的Workerman收到消息 → 写本地
Redis+publish到本地channel:msg→ 同时发一条AMQP消息到跨机房交换器 → 机房2的消费者收到后,再publish到本地channel:msg - 为防AMQP临时不可用,所有出站消息要先落本地
list队列(如queue:cross_dc_pending),由定时器每5秒重试一次
心跳与故障剔除必须独立于应用逻辑运行
Workerman默认的onClose回调不可靠:网络闪断、TCP FIN未送达、客户端强制杀进程等情况,onClose根本不会触发,导致Redis里的在线状态一直为真。
- 必须启用独立的心跳检测进程:用
Worker::runAll()启动一个专用Worker,每30秒扫描online:*key,比对last_heartbeat时间戳,超时则del并触发下线事件 - 心跳包不能走业务连接复用通道,要单独建一条
UDP或HTTP探测路径(如/healthz),避免和长连接争抢资源 - 机房级故障判断不能只看单台机器,要用
consul或nacos聚合本机房所有Workerman实例的/status响应,连续3次失败才标记该机房为不可用
最易被忽略的是跨机房时钟漂移——当两个机房的服务器时间差超过30秒,Redis的expire和心跳超时判断就会失准;必须统一用chrony指向同一个NTP源,且禁止手动date -s。

















