灰度发布必须绕开reload,因它仅替换子进程、不重建长连接,旧连接仍运行旧逻辑;websocket/tcp长连接需用nginx upstream权重分流或应用层配置中心按用户id路由。

灰度发布必须绕开 reload,它对长连接无效
Workerman 的 php start.php reload 只会替换子进程、重载动态 require 的代码,但不会重建连接对象,也不会触发 onConnect 回调。对长连接服务来说,已建立的连接仍跑着旧逻辑,新代码根本进不去——这不是 bug,是设计使然。
所以,长连接场景下强行用 reload 做灰度,结果就是:新用户连上新进程走新逻辑,老用户还在旧连接里跑旧代码,版本混杂、状态不一致、灰度不可控。
- reload 仅适用于 HTTP 短连接(如 API 接口),且要求业务逻辑全在
onMessage内动态加载 - WebSocket / TCP 长连接必须走连接级路由,不能依赖进程级 reload
- 如果 onMessage 里用了
include或require加载业务文件,reload 才可能生效;但连接上下文(如$connection->uid)仍是旧的
用 Nginx upstream + weight 实现最稳的灰度分流
这是生产环境最常用、最可控的方式:把新旧两个 Workerman 进程组注册为不同 upstream,用 Nginx 按权重分发流量。所有连接都经过 Nginx,它天然支持长连接透传(proxy_http_version 1.1 + proxy_set_header Connection '')。
示例配置片段:
upstream workerman_old {
server 127.0.0.1:2828 weight=90;
}
upstream workerman_new {
server 127.0.0.1:2829 weight=10;
}
server {
location /ws {
proxy_pass http://workerman_old;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
- 上线新版本时,先启
php start.php start -d监听新端口(如 2829),确认日志无报错 - 改 Nginx 配置,把 weight 从 0 调到 10%,
nginx -s reload生效,无需重启 Workerman - 观察监控:新端口的连接数、错误率、响应延迟是否正常;异常时直接调回 0%
- 注意:Nginx 默认不缓存 WebSocket upgrade 请求,但要确保
proxy_buffering off,避免粘包
应用层灰度:按用户 ID 路由需配合配置中心
如果需要更细粒度控制(比如只让 VIP 用户或内测账号走新版本),就得在 Workerman 应用内部做判断。但关键点是:判断逻辑不能写死,必须可热更新。
典型结构:
- 从请求中提取标识:
$uid = $connection->getCookie('uid') ?: $connection->getHeader('X-User-ID') - 查配置中心(如 Etcd)获取该 UID 对应的版本策略:
GET /gray/strategy?uid=12345 - 根据返回值决定走哪套业务逻辑:
if ($strategy === 'v2') { handle_v2($connection, $data); } - 务必加本地缓存(如 APCu)和超时降级:查配置中心失败时,默认走旧版,避免雪崩
不要用文件读取或数据库查询做策略源——IO 延迟高、不可靠、无法实时变更。配置中心必须支持监听变更(watch),策略更新后能立刻影响后续新连接。
长连接灰度最难的是连接生命周期管理
短连接灰度只需控制入口流量,而长连接灰度要面对一个现实:连接一旦建立,就可能持续数小时甚至数天。你无法强制断开用户连接来“切流”,只能等它自然关闭,或者设计优雅迁移路径。
- 新版本上线后,对已存在的旧连接,可通过
$connection->send()主动推送“即将升级,请刷新页面”提示 - 在
onClose回调里记录连接来源版本,用于统计灰度覆盖进度 - 严禁在灰度期间修改协议格式(如消息体 JSON 结构),否则新旧版本客户端/服务端会解析失败
- 如果用了 GatewayWorker 架构,灰度只需部署新 BusinessWorker,Gateway 层通过
gateway->bindUid()和路由规则就能隔离流量,比单 Worker 更灵活
真正容易被忽略的,是连接关闭后的资源清理一致性:旧版本连接释放的 Redis 键、MySQL 临时表、内存缓存,新版本必须能识别并接管,否则会出现“用户已登出但消息还在推”的诡异现象。











