< 返回版块

lithbitren 发表于 2024-06-15 15:05

Tags:axum,hyper,alloc,内存分配器


  • 前情提要:

如何看待axum/hyper疑似内存泄露的问题?

  • 原帖省流:

有人在知乎再次发现了axum/hyper的内存泄漏问题。坛内大佬们找到了原始issue,基本的结论是更换成MiMalloc内存分配器会最大程度的降低泄漏可能性。


  • 测试准备:

在VMware的虚拟机里测了一下,环境是ubuntu 24.04桌面版,分配2.6Ghz8C8G。

rust是最新版本rustc 1.81.0-nightly (b5b13568f 2024-06-10)--release模式。

所有终端都是ulimit -n 65536开启最大文件数。

同环境下直接访问http。

服务端axum写了一个最基础的hello,分别测的是默认内存分配器,以及前一个帖子里提到的MiMalloc、JeMalloc、Dhat一共四种内存分配模式。

首先试了下性能,先用oha随便打了下默认模式,改改oha客户端并发参数,报告axum最大qps能到130000+。

计划客户端用reqwest直接秒内1000/10000qps并发打进axum里,用tokio的interval每秒打一次,连续打到一分钟以上。

代码用的就是官方范例,全都tokio异步化,不存在同步阻塞代码,报告打印皆是一秒一次,基本不影响并发性能。

此轮测试没有用脚本检测内存,就用自带的系统监视器看了一下,截了一些图,但论坛不好传图,就四舍五入分享一下数据。


  • 测试结果:

启动的时候MiMalloc模式400+K内存,默认分配器、JeMelloc、Dhat都是不到300K内存。

客户端持续1000qps时,大概几秒后,MiMalloc的内存就会在30+M这样,其他三者在50~60M这样。

客户端持续10000qps时,大概几秒后,MiMalloc的内存会在300+M这样,其他三者在500~600M这样。

客户端压测结束时,所有内存分配模式的服务端,都会保持高内存占用,如果没有新请求,短时间内不会自动释放内存。

但此时低频率的发送请求(间歇性1~100qps),则会逐步释放内存。

  • 经过连续1000qps和10000qps且又间歇性的低频请求后,某次测试过程的各阶段近似状态:
状态--------(0qps)----(1000qps)-(10000qps)--(10qps)   

默认分配器:   300K  ->   60M  ->   600M  ->   560M

dHat:        300K  ->   60M  ->   600M  ->   560M

MiMalloc:    500K  ->   35M  ->   350M  ->   9M

JeMalloc:    300K  ->   56M  ->   560M  ->   30M

  • 内存问题成因推测:

首先,必须得承认axum/hyper确实存在内存分配问题,在经过多轮请求后,并没有恢复到接近最初的内存状态。

在默认分配器的情况下,用reqwest以1000qps打了axum两个小时,72万次请求,内存还是在55~60M这样。

内存无法正确回收,疑似和请求并发数有关,和总请求数关系甚微。

Dhat和默认分配器表现基本一致,对Dhat不太熟,查看了大致文档,感觉还是以监控为主,对内存分配释放并没有什么优化。

MiMalloc和JeMalloc都比默认分配器更好的释放内存,尽管两者最终内存状态仍有区别,但基本都可以认为在承受范围之内。

测试结果和hyper的issues里讨论结果基本一致。


  • 测试中遇到一些其他问题:

因为在同一个环境里,CPU爆表以及端口拥堵会同时影响到客户端和服务端。

虽然reqwest用了tokio的semaphore来控制并发,但如果瞬时并发数过大,CPU就会跑满,客户端这边就会产生大量send error,具体原因不明,猜测可能是TCP端口分配满了。

在这个环境下,reqwest大约能短时间连续秒内发28000不报错,但几秒后都会出现大量send error,会阻塞系统tcp通道,服务器即使没挂也无法提供服务。

所以最后决定只测最高10000qps的情况,基本能实现秒内吞吐,大约200~300ms就能完成所有请求的收发。

在CPU没爆表的情况下,各个内存分配器的服务端吞吐性能基本没有太大区别。

但即使是10000qps,跑了一段时间后,仍然有可能CPU爆表,挤压请求。

在连续10000qps的情况下,

其中默认分配器和Dhat大约十几秒后CPU开始跑满,开始出现send error的报错;

MiMalloc大约三十几秒后CPU开始跑满,开始出现send error的报错;

JeMalloc大约八十多秒后CPU开始跑满,开始出现send error的报错。

CPU跑满时,axum服务端会无法访问,在客户端停止发送请求后,服务端会在几秒内恢复正常。


  • 结论:

根据这个测试过程,axum即使存在内存分配问题,但看起来内存并不会无限增加。

如果能够承受生产环境下最大qps所占用的内存,也可以选择不更换内存分配器。

如果需要内存预热,则需要用压测来进行预热。

MiMalloc和JeMalloc都是axum服务端内存分配优化的可选项,

MiMalloc在测试中对内存分配释放表现更好,

JeMalloc在兼顾内存释放的前提下,CPU占用率表现更好。


评论区

写评论
作者 lithbitren 2024-06-17 09:41

我今天9:20开压,2000qps,开局262k,五分钟后9:25,内存稳定在77M-81M之间,现在9:45,还是79.5M左右。

计划开十个小时,下午再看看。

--
👇
zzl221000: 我记得这个问题就是每个新连接会占用内存,tcp复用就不会出问题 而且这个bug是内存会无限增加,只要你有足够多的连接 当时每天会校验100万个http代理,2000qps,几个小时程序就会oom 那会还在axum写了issue反馈,最后排查到是hyper的问题 https://github.com/tokio-rs/axum/issues/347#issuecomment-928090153 这里还有一份当时的 heaptrack ,可惜不会分析 heaptrack

zzl221000 2024-06-16 23:17

我记得这个问题就是每个新连接会占用内存,tcp复用就不会出问题 而且这个bug是内存会无限增加,只要你有足够多的连接 当时每天会校验100万个http代理,2000qps,几个小时程序就会oom 那会还在axum写了issue反馈,最后排查到是hyper的问题 https://github.com/tokio-rs/axum/issues/347#issuecomment-928090153 这里还有一份当时的 heaptrack ,可惜不会分析 heaptrack

作者 lithbitren 2024-06-16 04:59

有时间可能监控排查一下客户端和tcp端口的分配问题,看看怎么单机持续发送几万的请求,更好的压测。

还有时间的话,可能测测actix和pingora的内存分配情况。

hyper的内存跟踪有点复杂,看issue里author搞了好几年都没跟踪出来我暂时就不费这个力了。

反正目前的解决办法就是在main.rs的空白处加两三行alloc声明,基本不费力。

文章翻译以后评论到相关issue里面了,希望hyper的团队能解决吧:

https://github.com/hyperium/hyper/issues/1790#issuecomment-2170644852

--
👇
JackySu: 牛逼 这测试的很详细 能不能加入memgrind看一下内存泄露呢 比如对比malloc demalloc次数之类的

JackySu 2024-06-15 17:52

牛逼 这测试的很详细 能不能加入memgrind看一下内存泄露呢 比如对比malloc demalloc次数之类的

1 2 共 24 条评论, 2 页