个人微信机器人怎么升级才不丢消息?优雅停机与滚动发布,3阶段切换
我们的消息服务早期发版用的是最原始的方式:直接重启进程。某次发版后客服反映有客户两小时前问的话没人回——重启那一瞬间正在处理的消息和还没应答的推送,全随着进程一起消失了。
消息服务不是普通 Web 服务:连接层随时可能在推消息,处理是异步的,粗暴重启丢的不是请求,是业务往来记录。整改后我们建立了一套发版纪律,零丢失跑了半年。
原理:三阶段切换
优雅停机的核心是让进程"善终":先停止接收新工作,再处理完手头的,最后退出。
-
阶段一摘流量:进程从负载均衡摘除,回调推送不再路由到它
-
阶段二排存量:等队列和正在处理的消息全部落库,设置超时上限
-
阶段三才退出:确认无在途工作后结束进程,日志记录退出原因
排空的对象要具体:按 Eyun 文档的使用建议,同一 wId 的发送要进入队列避免并发直发,所以进程里真正要排空的就是这些按 wId 分队列的待处理工作,而不是笼统的"等一下"。
核心实现:信号驱动的停机流程
def on_sigterm(signum, frame):
drain(stop_accepting=True) # 摘流量,按 wId 队列排空
close_workers(timeout=30) # 限时收尾,超时强落库
log.info("shutdown clean")
signal.signal(signal.SIGTERM, on_sigterm)
drain 负责排空各 wId 队列,close_workers 的超时是安全网:收尾不能无限等,超时就强制落库再退,宁可留"已接收未处理"的待续状态,也不丢记录。
滚动升级的幂等防线
滚动升级时新旧副本会短暂并存,同一条推送可能被投递到两边——这不是异常,是发布的正常代价,幂等必须扛住。幂等键选推送体里自带的 newMsgId(文档响应字段,十九位大数,业务侧按字符串处理):
| 防线 | 做法 | 拦住的问题 |
|---|---|---|
| 唯一索引 | newMsgId 建唯一约束 | 双副本重复落库 |
| 条件更新 | 流水状态更新带前置条件 | 重复触发业务动作 |
| 摘除确认 | 摘流量后核对推送停止 | 切换窗口的错位 |
我们的踩坑发生在防线一没布的时期:滚动升级后同一条客户消息进了两次工单系统,客服按两条单处理,客户收到两次回复。newMsgId 唯一索引上线后,这类问题在数据库层面绝迹。
与发版窗口的配合
滚动升级解决技术问题,发布纪律解决业务问题:
-
避开业务高峰,选凌晨或会话低谷时段
-
升级前确认所有实例在线、队列无积压,带病不发布
-
升级后发一条测试消息到文件传输助手走完整链路,确认无恙再放量
文档的快速开始也推荐用 filehelper 做首次验证——发版冒烟沿用同一个习惯,手机看到消息才算链路正常。
权衡:停机排空的超时怎么定
排空超时给太长,发布周期被拖长;给太短,收尾不彻底。我们的经验值是按各 wId 队列正常处理时长的两倍设置,配合监控观察实际排空耗时,每季度校准一次。没有普适数字,只有按自己链路的数据校准出来的数字。
总结
发版不丢消息靠三件事:三阶段停机流程、newMsgId 幂等防线兜住双副本窗口、发布纪律选对时机。消息服务的发版应该是无聊的——所有惊险都说明设计还有缺口。发送队列与限速要求见 Eyun 的 API 使用与安全,接入细节可通过 Eyun 平台咨询。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐




所有评论(0)