抖音实习生用Rust+Go将支付性能翻倍!年省30万

吴晓云讲述了在TikTok实习期间,他们如何将一个关键的Go支付服务的性能提高了一倍,每年节省了30万美元,并证明了Rust + Go可以成为一个强大的多语言策略。

2025年8月1日,北京,凌晨两点。办公室的灯还亮着,我盯着监控面板上那条熟悉的CPU曲线,心里却像被什么压着——又红了。那个我们用了好几年的Go支付服务,又一次触顶报警。这不是第一次,但这一次,我知道不能再靠加机器解决了。

这是我在TikTok实习期间最难忘的一段经历。我们的核心支付服务,原本是用Go写的,简洁、高效、上线快,支撑了TikTok LIVE庞大的实时打赏和账户体系。它像一辆跑惯了城市环路的老款SUV,平顺、可靠,可当车流量从每天十万辆涨到一百万,这条路就开始堵得水泄不通。

尤其是其中两个接口:查询用户余额和统计信息。这两个看似简单的API,每天要扛住上亿次调用。随着主播人数激增、粉丝打赏越来越频繁,这些请求像潮水一样涌进来。我们不断扩容,从40台机器加到80台,再加到120台,每加一次,账单就跳一次。运维同事开玩笑说:“咱们不是在做技术,是在给云厂商打工。”

更麻烦的是,性能瓶颈越来越明显。看火焰图的时候,整个屏幕几乎都被那几个函数烧红了——GC频繁触发、内存拷贝太多、并发锁竞争激烈。我们尝试过各种优化:缓存预热、结构体对齐、减少逃逸分配……但每次提升都像是在漏气的轮胎上贴胶布,治标不治本。

就在大家开始讨论要不要重构整个服务时,我的导师提了个问题:“有没有可能,我们不是选错了语言,而是用错了工具?”

这句话点醒了我。Go适合大多数场景,但不代表它适合所有场景。就像你不会用菜刀去砍树,也不会用斧头去切菜。我们真正需要的,不是换一辆车,而是给这辆车换上更强的引擎——只换最关键的那部分。

于是,我们决定做一件听起来有点“叛逆”的事:把那两个最吃CPU的接口,用Rust重写,其他部分依然保留在Go里。这不是全量迁移,而是一次精准打击。我们称之为“外科手术式重构”。

听起来简单,做起来却步步惊心。第一关,不是性能,是正确性。金融系统最怕什么?不是慢,是错。哪怕返回一分钱的误差,都可能引发连锁反应。所以我们没有急于上线,而是让Rust版本先在“影子模式”下运行——所有真实流量复制一份给它处理,但它不参与实际响应。然后我们写了一套比对系统,逐条校验Go和Rust返回的结果是否一致。

整整三周,每天几百万条请求,没有一条数据偏差。那一刻,我才真正松了一口气。代码可以快,但必须准。

第二关是压测。我们在生产环境搭了两个一模一样的集群,一个跑原版Go,一个跑新Rust服务,输入同样的用户数据、同样的请求节奏,一点点加压。当QPS冲到8万时,Go集群的CPU已经飙到78%,延迟开始抖动;而Rust那边,CPU才52%,p99延迟从原来的近20毫秒,降到了4.8毫秒。

最震撼的是极限测试。某个核心接口,Go最多撑到8.5万QPS就崩了,而Rust轻松突破15万。另一个接口更是直接翻倍:10.5万 vs 21万。这不是靠堆资源赢的,而是靠语言特性——零成本抽象、无运行时GC、极致的内存控制。我们甚至用了copy-on-write技术,避免不必要的数据复制,这些在Go里要么做不到,要么代价太高。

结果出来了:同样的硬件,Rust服务能扛两倍的流量,内存占用只有原来的三分之一,延迟压到原来的四分之一。这意味着什么?意味着我们不需要再买那么多服务器了。最终测算下来,每年能省下超过30万美元的云成本——相当于400多个vCPU直接下线,这笔钱够养一个小团队了。

但比省钱更让我感慨的,是这次经历带来的认知升级。很多人喜欢争论“Rust vs Go”,仿佛非此即彼。可现实中的工程,从来不是站队游戏。Go依然是我们内部95%服务的首选——它的开发效率、生态成熟度、团队熟悉度,都是无可替代的。正是因为它足够好,才让我们能把一个服务做到需要被优化的程度。

而Rust,更像是特种兵。平时不用,关键时刻顶得上。它不适合快速迭代,不适合写业务逻辑,但在那些对性能有极致要求的节点上,它就是一把出鞘的刀。

项目上线那天,我站在公司天台上看了会儿夜景。楼下快递小哥还在送餐,程序员们陆陆续续下班。这座城市从不停歇,就像我们的系统一样,永远在跑。但我知道,有些改变已经发生——不是靠蛮力,而是靠选择。

后来有新人问我:“你觉得以后是不是都要转Rust?”我笑了笑说:“不,我觉得以后我们会更懂Go。”因为只有真正理解一个工具的边界,才会知道什么时候该坚持,什么时候该突破。

这世上没有银弹,但有精准的子弹。有时候,最大的进步,不是推倒重来,而是在最关键的地方,轻轻扣下那一发。