最近在用 rust 的 salvo 框架重写我原来的一个用 go 写的图床项目,写完后测试过程中发现 rust 版本消耗的内存巨大,每次图片的请求都会增加内存而且请求结束后内存不会释放,每次内存的增加量与待处理的图片大小成正比
虽然后续测试发现内存会增加到接近内存的限制后停止增加,但毫无疑问其不释放内存的问题是明显存在的,这使得项目在无请求的状态下都能占用十几倍于 go 的项目的内存
不释放内存似乎是 rust web 框架的普遍问题
其实有关 rust 的 web 框架“内存泄漏”问题的讨论很早就开始了 → 如何看待 axum/hyper 疑似内存泄露的问题? - Rust 语言中文社区 (rustcc.cn),一开始就是因为爆出的这个问题导致我放弃使用还在 0.x 阶段的 axum 框架。文章中似乎将问题指向了内存分配器,认为是内存池没有释放内存导致的,更换内存分配器能使该问题得到一定程度改善,但内存不释放的问题依然存在
通过这篇文章 → rust 的 web 框架单机百万并发的性能与开销 - Rust 语言中文社区 (rustcc.cn) 可以看到该问题在 axum、actix 和 salvo 上都能复现,这让人不禁怀疑是不是 tokio 异步运行时的问题。在 tokio 的仓库能够看到一些关于 memory leak 的 issue,其中 tokio task spawn memory “leak” · Issue #4406 · tokio-rs/tokio (github.com) 指出该问题与内存分配器关联,但无法确认内存分配器没有正确释放内存的原因,该 issue 可以总结为 “ tokio 在某种场景下似乎触发了 Linux 下默认 rust 内存分配器的 bug ”,需要注意的是该问题导致了 issue 提出者的项目出现了内存溢出错误
tokio 仓库有关该问题的 issue 基本都被关闭了(项目维护者认为该问题不是 tokio 自身的问题),几个 web 框架关于该问题的讨论还在继续,Memory Leak · Issue #3198 · actix/actix-web (github.com)
该如何解决(缓解)该问题
目前普遍认为更换为 MiMalloc 或 JeMalloc 内存分配器能很大程度上缓解该问题,根据测试更换分配器后,在大量请求完成后,内存会恢复到一个能够接受的范围,虽然依旧存在没有释放完毕的内存。希望在后续有关该问题的讨论中能提出一个确定的关于导致该问题的原因和解决办法,不然很难让人相信 rust 能在生产环境用于 web 项目
在彻底定位和解决该问题前,使用 JAVA 或 GO 等带有 GC 的语言可能是比较稳定的做法