修复Rust中的内存泄漏 - onesignal


在 OneSignal,我们喜欢 Rust。我们之前写过博客,介绍了将我们的一些核心业务系统转为 Rust该语言在过去几年中发生了怎样的变化,以及我们了解到的关于线程安全模型的有趣事情。除了我们的推送通知传递系统的核心 Onepush 之外,我们还将 Rust 用于 gRPC 服务和多个 Kafka 消费者。在本文中,我们将讨论我们在最新的 Rust 项目之一中遇到的一个绊脚石。

挑战
接近 2 月底,我们宣布全面推出“旅程”功能。Journeys 允许客户使用无代码 UI 轻松构建复杂的消息传递工作流。它由几个 gRPC 服务和一个用 Rust 编写的 Kafka 消费者(称为 JourneyX)提供支持。Journeys 是一个事件驱动的系统,因此在旅程工作流中发生的所有事情都是由 Kafka 流上的事件触发的。这些事件由 JourneyX(旅程执行者)使用。

几周前,随着 Journeys 的采用率开始增加,且 JourneyX 开始处理更多事件,我们开始注意到其内存使用情况令人不安。最活跃的进程一直在使用大量内存,然后被内核杀死。Linux 内核有一个称为 OOM(内存不足)杀手的功能,当进程消耗过多的系统内存时,它会自动终止进程。这可以防止系统因资源不足而变得不稳定或锁定。在我们的案例中,JourneyX 进程不断地被 OOM 杀手杀死、重新启动和再次杀死。当进程被终止时,这在图表上显示为快速分配和近乎即时释放的锯齿模式。在最繁忙的进程中,内存使用量会飙升至 17 GiB,

这给我们带来了一些问题。显然,从理论的角度来看,我们不希望一个系统不断地被操作系统杀死。它所执行的操作是空闲的,所以我们不一定担心会因为这个问题而发送多个通知。这个问题确实给我们带来了垃圾警报,这导致我们对内存进行了过度配置。持续的崩溃也引起了对服务的长期健康的担忧。

但是,等一下!我听到你说--难道不是吗?我听到你说--Rust的借用检查器不是可以防止内存错误吗?Rust不是应该是 "安全 "的吗?事实证明,根据Rust的规则,泄露内存是完全安全的!事实上,我们可以有目的的去泄露内存。事实上,我们可以使用函数std::mem::forget有目的地泄露我们想要的内存。
关于内存泄露的唯一 "不安全 "之处在于,它们可能最终导致你的程序被内核杀死(正如JourneyX在本例中所做的那样)。一个程序以可预测的方式结束也被认为是安全的行为。

Rust的安全保证是为了保护我们免受无效的内存访问,而不是耗费资源。

Rust 有很多东西,但它并不是真正的“运行时可配置”。有一些库允许您编写可在运行时配置的 Rust 代码,但大多数 Rust 代码不是以这种方式编写的。因此,我们需要向 Rust 应用程序添加大量手动检测。

详细点击标题