如何找到扭曲服务器内存使用增加的来源?

17 投票
3 回答
3058 浏览
提问于 2025-04-15 18:14

我有一个用Python写的音频广播服务器,基于Twisted框架。它运行得很好,但当服务器上的用户增多时,内存使用量也在增加。而当这些用户下线后,内存使用量却从来没有下降。你可以在下面的图中看到:

alt text

从图中可以看出,当听众/广播的数量上升时,内存使用量的曲线也随之上升。但在听众/广播数量达到峰值后,内存使用量依然很高,始终没有下降。

我尝试了以下方法来解决这个问题:

  1. 将Twisted从8.2升级到9.0
  2. 使用guppy来查看内存使用情况,但没有任何帮助
  3. 将选择器反应器切换到epoll反应器,问题依旧。
  4. 使用objgraph绘制对象关系图,但我看不出什么有用的信息。

这是我运行Twisted服务器的环境:

  • Python: 2.5.4 r254:67916
  • 操作系统: Linux版本 2.6.18-164.9.1.el5PAE (mockbuild@builder16.centos.org) (gcc版本 4.1.2 20080704 (Red Hat 4.1.2-46))
  • Twisted: 9.0 (在virtualenv下)

以下是guppy的内存转储:

Partition of a set of 116280 objects. Total size = 9552004 bytes.
 Index  Count   %     Size   % Cumulative  % Type
  0  52874  45  4505404  47   4505404  47 str
  1   5927   5  2231096  23   6736500  71 dict
  2  29215  25  1099676  12   7836176  82 tuple
  3   7503   6   510204   5   8346380  87 types.CodeType
  4   7625   7   427000   4   8773380  92 function
  5    672   1   292968   3   9066348  95 type
  6    866   1    82176   1   9148524  96 list
  7   1796   2    71840   1   9220364  97 __builtin__.weakref
  8   1140   1    41040   0   9261404  97 __builtin__.wrapper_descriptor
  9   2603   2    31236   0   9292640  97 int

从中可以看到,总大小为9552004字节,也就是9.1 MB,你可以看到通过ps命令报告的rss:

[xxxx@webxx ~]$ ps -u xxxx-o pid,rss,cmd
  PID   RSS CMD
22123 67492 twistd -y broadcast.tac -r epoll

我服务器的rss是65.9 MB,这意味着在我的服务器上有56.8 MB的不可见内存使用,这些是什么呢?

我有以下几个问题:

  1. 如何找到内存使用量增加的来源?
  2. guppy能看到的内存使用量是什么?
  3. 那些不可见的内存使用量是什么?
  4. 这是否是某些用C语言编写的模块造成的内存泄漏?如果是的话,我该如何追踪和修复?
  5. Python是如何管理内存的?是内存池吗?我觉得这可能是音频数据块造成的。所以在Python解释器拥有的内存块中可能有小的泄漏。

更新 2010/1/20: 有趣的是,我下载了最新的日志文件,显示内存在某个时刻并没有增加。我想可能是分配的内存空间足够大。以下是最新的图。

alt text

更新 2010/1/21: 这里还有另一张图。嗯……稍微有一点上升。

alt text

哎呀……还是在上升。

alt text

3 个回答

0

你有没有考虑过使用CentOS的一个替代工具,叫做SystemTap

这个工具可以让你比较深入地了解你系统里发生了什么,尤其是在你的*nix进程内部……虽然有点不确定,但可能会让你更清楚进程之间的活动。

这个问题很有意思。期待看到其他人的回复。

Ben

2

听起来像是C语言模块中的内存泄漏。Valgrind是一个很好的工具,可以帮助你找出和内存分配相关的问题。不过,我不太确定它在处理运行时加载的模块时效果如何……

6

根据我的猜测,这个问题是由于内存碎片化造成的。最初的设计是把音频数据分成一块一块的存放在一个列表里,这些块的大小都是不固定的。当这个列表的总大小超过了缓冲区的限制时,就会从列表的顶部弹出一些块,以控制大小。它可能看起来像这样:

  1. 块大小 511
  2. 块大小 1040
  3. 块大小 386
  4. 块大小 1350
  5. ...

大部分块的大小都超过了256字节,Python在处理超过256字节的块时,会使用malloc而不是内存池。你可以想象这些块是如何分配和释放的,那会发生什么呢?比如,当1350字节的块被释放时,堆中可能会留下1350字节的空闲空间。接着,如果又来了一个请求需要988字节,malloc就会利用这个空洞,然后又会留下一个362字节的小空洞。随着时间的推移,堆中会出现越来越多的小空洞,换句话说,堆中会有很多碎片。虚拟内存的页面大小通常是4KB,这些碎片分布在堆的一个大范围内,这让操作系统无法将这些页面交换出去。因此,RSS(常驻集大小)总是很高。

在我对服务器的音频块管理模块进行修改后,现在使用的内存变得很少。你可以看看这个图,并与之前的图进行比较。

alt text

新的设计使用了bytearray而不是字符串列表。这是一块大的内存,所以不再有碎片问题。

撰写回答