用 Go 重写了公司的推送网关

折腾了三个月的事终于上线了:把老的推送网关从 Java 迁到了 Go。这篇记录一下过程和数据,也算给自己一个交代。 为什么要迁。 老网关是五年前写的,Netty + 一堆历史包袱,单机维持 5 万长连接就很吃力,每次大促前都要提前扩容到 20 台机器,还经常半夜被告警叫起来。不能优雅重启是压死骆驼的最后一根稻草:每次发版都断推送几分钟,运营那边意见很大。 新架构。 接入层用 Gnet 做自定义协议解析(老客户端协议不能动,这点很烦),业务逻辑层独立部署,中间用 NATS 解耦。连接状态存 Redis。选 Gnet 而不是直接用标准库 net,是因为老协议里有粘包处理和一些奇怪的字节操作,想要更细的控制粒度。 效果数据(灰度一周后的均值): 单机长连接:5 万 -> 22 万,内存占用反而从 8G 降到 3G; 发版不再断连:连接迁移方案虽然土(客户端自动重连 + 状态外置),但实测断连窗口小于 2 秒; 机器数:大促预留从 20 台降到 6 台。 goroutine 泄漏的教训这次也吃了个现场版:压测到 30 万连接的时候内存突然起飞,pprof 看下去是心跳超时后的清理 goroutine 阻塞在往一个满载的 channel 里写。改成 select 加 default 分支,接受偶尔的清理延迟,问题消失。在连接数这个量级上,任何"看起来没关系"的分配都要重新审视,一个连接 2KB 的额外开销就是 600MB。 写 Java 的老同事问我是不是 Go 天生就这么快。我的回答是:语言贡献了一部分,但更大头是老系统这些年积累的暗坑全被我们重新设计掉了。重写永远比改造爽,因为你是带着答案去写题的。

2022-10-27 · 芮靖安