wangyibo00 2019-10-06
我曾经开发过的几个大型Django应用程序都在某个时候出现了内存泄漏。Python进程缓慢地增加它们的内存消耗,直到崩溃。这一点也不好玩。即使自动重新启动进程之后,仍然会有一些宕机问题。
Python中的内存泄漏通常发生在无限增长的模块级变量中。这可能是一个具有无穷大maxsize的lru_cache变量,也可能是一个在错误范围内声明的简单列表。
泄漏也不是只有发生在您自己的代码中才会影响您。例如,看看BuzzFeed的Peter Karp写的这篇优秀的文章,他在Python的标准库中发现了一个内存泄漏(已经修复了!)
下面的解决方法都会在执行了很多请求或任务之后重新启动worker进程。这是一个清除任何潜在的无限积累的Python对象的简单方法。如果您的web服务器、队列worker或类似的应用程序有此能力,但还没有被功能化,请告诉我,我会添加它!
即使您现在还没有看到任何内存泄漏,添加这些解决方法也会提高您的应用程序的弹性。
如果您正在使用Gunicorn作为您的Python web服务器,您可以使用--max-requests设置来定期重启worker。与它的兄弟--max-requests-jitter配合使用以防止所有worker同时重启。这有助于减少worker的启动负载。
例如,在最近的一个项目中,我将Gunicorn配置为:
对于此项目的流量水平、worker数量和服务器数量来说,这将大约每1.5小时重新启动worker。5%的浮动足以消除重启负载的相关性。
如果您正在使用uwsgi,您可以使用它类似的max-requests设置。此设置在很多次请求之后,也会重新启动worker。
例如,在以前的一个项目中,我在uwsgi.ini文件中像这样使用了这个设置:
Uwsgi还提供了max-requests-delta选项用于添加其他浮动。但由于它是一个绝对数字,所以配置起来要比Gunicorn更麻烦。如果您更改了worker的数量或max-requests的值,那您就需要重新计算max-requests-delta来保持您的浮动在一个特定的百分比。
Celery为内存泄漏提供了几个不同的设置。
首先是worker_max_tasks_per_child设置。这将在worker子进程处理了许多任务之后重新启动它们。此设置没有浮动选项,但是Celery任务的运行时间范围很广,所以会有一些自然的浮动。
例如:
或者您正在使用Django设置:
100个任务比我上面建议的web请求数要小一些。在过去,我最终为Celery使用了较小的值,因为我在后台任务中看到了更多的内存消耗。(我想我还在Celery本身中遇到了内存泄漏。)
您可以使用的另一个设置是worker_max_memory_per_child。这指定子进程在父进程替换它之前可以使用的最大千字节内存。它有点复杂,所以我还没用过。
如果您确实使用了worker_max_memory_per_child设置,那么您可能应该将其计算为总内存的百分比,并除以每个子进程。这样,如果您更改了子进程的数量或您的服务器的可用内存,它将会自动扩展。例如(未测试):
这使用psutil来查找整个系统内存。它将最多75%(0.75)的内存分配给Celery,只有在服务器是一个专用Celery服务器的情况下您才会需要它。
在Python中调试内存泄漏是很不容易的,因为任何函数都可以在任何模块中分配全局对象。它们也可能出现在与C API集成的扩展代码中。
我用过的一些工具:
更新 (2019-09-19): Riccardo Magliocchetti在Twitter上提到,pyuwsgimemhog项目可以解析uwsgi日志文件,并告诉您哪些路径正在泄漏内存。很简洁!
其他一些有用的博文:
祝您内存泄漏更少!
—Adam
英文原文:https://adamj.eu/tech/2019/09/19/working-around-memory-leaks-in-your-django-app/
译者:野生大熊猫