小李把Go单体重写成Rust Axum,公司一年省了800多万,他涨薪到200万

小李那个让我意外的涨薪

上周小李约我吃饭,这小子平时抠门得很,主动请客肯定有事。

果然,酒过三巡他开口了:“我把公司后端从Go重写成Rust了,一年省了800多万,公司给我涨薪到200万。”

我差点没把筷子掉桌上。这小子我知道,技术是不错,但也没夸张到这种程度。

“你先别急着羡慕,“小李干了杯啤酒,“这事儿不能简单看。大多数重写项目最后都变成了职业生涯的坑——进度慢、风险大、锅背不完。我这次能全身而退,是因为情况刚好满足了几个条件。”

他们原来的系统有多烂

小李接手那个Go单体的时候,系统已经运转好几年了。代码写得其实还行,不是什么烂摊子。但就是——怎么说呢——累了。

“就像你家那台用了五六年的冰箱,“小李说,“没坏,但总是嗡嗡响,耗电比以前高,有时候制冷不太给力。你说它坏了吧,还能用;说它好吧,心里总不踏实。”

经过分析,这是一个典型的技术重写案例,原系统存在性能优化空间,同时成本优化的需求也很迫切。

看几个数字:

  • 峰值能抗住2.8万请求每秒
  • 每周大概出三次故障
  • 每个月基础设施费用130多万
  • 团队里慢慢形成了"那块代码别碰"的默契

这些问题都指向一个技术重写的必要性,同时也能带来显著的性能优化成本优化效果。

大多数故障不是那种惊天动地的宕机。是延迟抖动、部分服务变慢、重试风暴之类的。用户觉得慢,但不至于打客服电话。这种问题最麻烦——不够严重所以没人批预算修,但一直在消耗团队精力。

[客户端]
   |
[负载均衡]
   |
[Go 单体]
   |      |      |
  认证   搜索   计费
   |
[主数据库]

所有东西挤在一起。想优化搜索,得考虑会不会影响计费。想上新功能,得把整个系统测一遍。每次扩容,所有模块一起扩,但其实只有搜索需要更多资源。

“这种架构不是不能用,“小李说,“而是到了一定规模,钱就开始哗哗流。通过微服务架构改造,可以实现更精细的资源管理和更高效的性能优化。”

这就是为什么小李决定进行Go语言迁移到Rust的决策背景。

学Rust的那三个月有多痛苦

小李没一上来就动生产代码。前三个月全是晚上和周末自己学,公司一行代码没改。

从Go过来学Rust,有些地方特别别扭。Go的并发模型,开个goroutine就完事了,runtime帮你调度。Rust不是这样——你得明确告诉它这个任务什么时候跑、在哪跑、数据归谁管。

“一开始我觉得这是Rust在折腾人,“小李说,“学了一阵子才明白,这其实是把Go藏在runtime里的复杂性给拎出来了。Go的调度器确实优秀,但它帮你做的决定,你看不见也改不了。Rust让你直面这些决定——累是累点,但出了问题你知道往哪查。”

这次Go语言迁移到Rust的技术重写过程,让小李深刻体会到了两种语言的差异。

举个具体的:Go里goroutine阻塞了,runtime会自动切换。你可能根本不知道哪里阻塞了,直到某天流量上来卡住。Rust的Tokio不会帮你藏这些,阻塞调用会直接把worker线程卡死,你立刻就会发现。

“这种’不帮你藏问题’的设计哲学,“小李说,“后来救了我们好几次,也是这次性能优化成功的关键因素。”

第一刀只切搜索

小李他们没搞大爆炸式重写。第一刀只切搜索服务。

为什么选搜索?几个原因:读多写少,状态简单;API边界清晰,不用动其他模块;是当时最吃资源的部分;出了问题能快速回滚到Go版本。

[客户端]
   |
[负载均衡]
   |
[Go 单体] ---> [Rust Axum 搜索服务]
      |
   [旧数据库]

两个系统并行跑了一个月。用流量镜像对比结果,确保Rust版本返回的数据和Go版本一模一样。

结果让小李自己都有点意外:搜索吞吐量从2.8万飙到12万请求每秒,大概4.7倍的提升,高负载下P99延迟更稳定。

但小李很实在:“这个数字有水分。新系统做了更激进的缓存,请求路径更短,数据模型也针对搜索优化过。不能简单说Rust比Go快4.7倍。”

无论如何,这次微服务架构改造带来的性能优化效果是显著的。

真正让他惊喜的不是速度,是稳定性。以前流量高峰会触发重试风暴,然后整个系统一起抖。换成Rust之后,搜索服务在压力下表现得特别——无聊。就是稳稳地跑,没什么drama。

“无聊,“小李说,“在后端工程里是最高的赞美。”

一年时间慢慢磨

搜索服务稳定跑了三个月,领导开始问:其他部分要不要也换?

接下来一年小李他们做的事情可以总结成一句话:永远保持两条腿能跑。

阶段1: [客户端] -> [负载均衡] -> [Go 单体]
阶段2: [客户端] -> [负载均衡] -> [Go 单体] -> [Rust 微服务]
阶段3: [客户端] -> [负载均衡] -> [Rust 微服务] -> [Go 残留]

架构演进三阶段

通过这种渐进式的Go语言迁移策略,他们成功构建了微服务架构,实现了显著的成本优化

“Istio在这个过程中帮了大忙,“小李说,“我们可以按百分比切流量,某个接口出问题就在网格层回滚,不用动代码重新部署。”

OpenTelemetry是另一个功臣。Go的trace也做了,但Rust的span有个特点——生命周期跟代码结构绑定。资源泄漏、阻塞调用、请求扇出,在trace里一目了然。以前排查问题是破案,现在是看图说话。

这种可观测性改变了他们处理故障的方式。以前是"又出问题了,这次什么症状”;现在是"图上这里异常,查一下是不是那个逻辑”。

最后的数字

迁移全部完成后:

指标Go 单体Rust 微服务
峰值吞吐量2.8万 RPS18.7万 RPS
月基础设施成本130万30万
年节省-约800万
生产故障每周约3次14个月零故障

成本优化仪表盘

130万降到30万,一年就是800万的差价。但这不是"Rust快"带来的,是几个因素叠加的结果:异步调度可预测,没有隐藏的runtime开销;Axum中间件支持显式背压控制;服务拆小了,微服务架构带来更好的故障隔离;缓存命中率上去了,重试少了,实现了显著的性能优化成本优化

当然也有代价。编译时间变长了,招Rust工程师比招Go工程师难,新人上手需要更多系统编程基础。有些内部工具他们没动,继续用Go,因为技术重写它们不划算。

为什么这次重写能变成涨薪

“涨薪不是因为Rust牛,“小李说,“是因为这次重写改变了几个对公司来说很重要的东西。”

每年800万的成本节省是可审计的,财务能直接从账单上看到;以前每周三次故障,运维团队长期疲于奔命,现在这些人力释放了;产品上新功能不用再担心"会不会把后端搞挂”;容量规划会议从"要加多少机器"变成"现在资源够用一年”。

“大多数技术重写止步于’它能跑了’,“小李说,“这次Go语言迁移改变了公司的成本结构和运营节奏。这才是领导会注意到的。涨薪不是奖励,是公司重新评估了我的经济贡献。”

那几件他不会再做的事

小李说如果重来一次,有些事会做不同的处理。

第一,低估了重写带来的社会成本。团队里有人担心自己被边缘化,有人觉得自己的代码被否定了。这些情绪需要花时间处理,但他当时太专注技术了。

第二,Rust不适合所有场景。后台管理界面、低频批处理任务,他现在不会去碰。重写它们的收益太小,不值得承担风险。这次技术重写的经验告诉他,选择合适的场景比技术本身更重要。

第三,没有服务网格和分布式追踪,不要开始Go语言迁移。他们能做到零故障切换,全靠Istio和OpenTelemetry。没有这些基础设施,“平滑迁移"就是一句空话。

你应该做吗

“我临走时问小李,那我们公司要不要也试试?“我回忆着饭局的情景。

他反问我:“你们现在痛吗?”

如果系统运行稳定、成本可接受、团队不疲惫,那技术重写就是制造问题而不是解决问题。不是所有系统都需要重写,大多数系统其实都不需要。

Go语言迁移到Rust这样的技术重写只在一种情况下有意义:业务已经感受到了痛,而且愿意承担一种新的风险来消除旧的痛,同时期待通过这次性能优化成本优化带来实际的收益。

“我们的情况刚好满足这个条件,“小李说,“你们的情况呢?”


觉得这篇文章有用吗?

如果你的公司也在考虑技术栈迁移,或者对Rust和Go的实际生产对比感兴趣,欢迎点赞让更多人看到。

有什么问题或者想法?欢迎在评论区聊聊。你们团队遇到过什么样的技术债务?最后是怎么处理的?

关注梦兽编程,下一篇我们聊聊Rust异步编程的几个容易踩的坑,以及如何用Tokio Console来调试它们。