<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>实战 on 山行小记</title>
    <link>https://ruijingan.pages.dev/tags/%E5%AE%9E%E6%88%98/</link>
    <description>Recent content in 实战 on 山行小记</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <copyright>@2025</copyright>
    <lastBuildDate>Thu, 27 Oct 2022 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://ruijingan.pages.dev/tags/%E5%AE%9E%E6%88%98/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>用 Go 重写了公司的推送网关</title>
      <link>https://ruijingan.pages.dev/posts/2022/push-gateway-rewrite/</link>
      <pubDate>Thu, 27 Oct 2022 00:00:00 +0000</pubDate>
      <guid>https://ruijingan.pages.dev/posts/2022/push-gateway-rewrite/</guid>
      <description>&lt;p&gt;折腾了三个月的事终于上线了：把老的推送网关从 Java 迁到了 Go。这篇记录一下过程和数据，也算给自己一个交代。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么要迁。&lt;/strong&gt; 老网关是五年前写的，Netty + 一堆历史包袱，单机维持 5 万长连接就很吃力，每次大促前都要提前扩容到 20 台机器，还经常半夜被告警叫起来。不能优雅重启是压死骆驼的最后一根稻草：每次发版都断推送几分钟，运营那边意见很大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;新架构。&lt;/strong&gt; 接入层用 Gnet 做自定义协议解析（老客户端协议不能动，这点很烦），业务逻辑层独立部署，中间用 NATS 解耦。连接状态存 Redis。选 Gnet 而不是直接用标准库 net，是因为老协议里有粘包处理和一些奇怪的字节操作，想要更细的控制粒度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;效果数据&lt;/strong&gt;（灰度一周后的均值）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单机长连接：5 万 -&amp;gt; 22 万，内存占用反而从 8G 降到 3G；&lt;/li&gt;
&lt;li&gt;发版不再断连：连接迁移方案虽然土（客户端自动重连 + 状态外置），但实测断连窗口小于 2 秒；&lt;/li&gt;
&lt;li&gt;机器数：大促预留从 20 台降到 6 台。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;goroutine 泄漏的教训这次也吃了个现场版：压测到 30 万连接的时候内存突然起飞，pprof 看下去是心跳超时后的清理 goroutine 阻塞在往一个满载的 channel 里写。改成 &lt;code&gt;select&lt;/code&gt; 加 default 分支，接受偶尔的清理延迟，问题消失。&lt;strong&gt;在连接数这个量级上，任何&amp;quot;看起来没关系&amp;quot;的分配都要重新审视&lt;/strong&gt;，一个连接 2KB 的额外开销就是 600MB。&lt;/p&gt;
&lt;p&gt;写 Java 的老同事问我是不是 Go 天生就这么快。我的回答是：语言贡献了一部分，但更大头是老系统这些年积累的暗坑全被我们重新设计掉了。重写永远比改造爽，因为你是带着答案去写题的。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
