Django Python的垃圾回收难题

13
经过两天的调试,我找到了耗费时间的问题:Python垃圾回收器。 我的应用程序在内存中保存了大量对象,并且运行良好。 GC执行常规循环(我没有更改默认阈值(700,10,10))。 偶尔,在重要事务进行中,第二代扫描会启动并检查我的1.5M第二代对象。 这需要2秒钟! 我的问题是我该怎么办? 我可以关闭第二代扫描(通过设置非常高的阈值-这是正确的方法吗?),而GC是顺从的。 我什么时候应该开启它们? 我们使用Django实现了Web服务,每个用户请求大约需要0.1秒。 最理想的情况是,在用户API请求之间运行这些GC gen 2循环。但是我应该如何做到呢? 我的视图以 return HttpResponse() 结束,之后我想运行一个gen 2 GC清除操作。 我该如何做到?这种方法是否有意义? 我可以标记那些永远不需要被垃圾回收的对象,以便GC不需要在每个第二代周期中测试它们吗? 当Django服务器相对空闲时,我该如何配置GC进行完整的清除操作? Python 2.6.6在多个平台上(Windows / Linux)运行。

我的应用程序在内存中保存了很多对象。如何处理? - S.Lott
这些容器是标准的字典。这些对象本身要么是我的自定义类实例(派生自object),要么是元组,其中一个项目是对该类实例的引用(其余项目是整数)。 - Tal Weiss
由于Django的请求和响应对象是瞬时的,那么如何在内存中保存任何内容呢? - S.Lott
@S.Lott:例如将字典放置在模块的命名空间中。并不是所有东西都必须存在于请求/响应周期中。 - Benjamin Wohlwend
@piquadrat:正确。还有其他方法。使用模块代替会话可能适用于这个问题,也可能不适用。了解确切的情况比猜测更重要。没有更多证据,我不容易相信是垃圾回收的问题。 - S.Lott
@piquadrat 是正确的。我在模块的命名空间中使用了一个全局对象。这个对象除了庞大之外,还需要很长时间来初始化,这也是我最初将其设置为全局的原因。顺便说一句 - 它也是常量。 - Tal Weiss
5个回答

8

我们曾经为gunicorn做过类似的事情。根据你使用的wsgi服务器,你需要找到响应之后的正确挂钩点,而不是之前的。Django有一个request_finished信号,但这个信号仍然是在响应之前。

对于gunicorn,在配置中你需要定义2个方法,如下所示:

def pre_request(worker, req):
    # disable gc until end of request
    gc.disable()


def post_request(worker, req, environ, resp):
    # enable gc after a request
    gc.enable()

post_request 在 HTTP 响应发送完成后运行,这是进行垃圾回收非常好的时机。


4

我认为一种选择是完全禁用垃圾回收,然后像这里建议的那样在请求结束时手动进行回收:垃圾回收机制是如何工作的?

我想你可以在settings.py文件中禁用GC。

如果您想在每个请求上运行GarbageCollection,我建议开发一些中间件,在process response方法中执行它:

import gc
class GCMiddleware(object):
    def process_response(self, request, response):
        gc.collect()
        return response

虽然我还没有实现这个,但它看起来是正确的方法。 - Tal Weiss
4
不,这不是正确的方法。它在返回响应之前收集垃圾回收,因此仍然会阻塞响应的返回。 - dalore

1

另一种选择可能是完全禁用GC,并配置mod_wsgi(或您正在使用的任何其他工具)更频繁地杀死和重启进程。


0

基于 @milkypostman 的方法,您可以使用 gevent。 您想要每个请求 执行一次 垃圾回收,但是 @milkypostman 的建议存在问题,即调用 gc.collect() 仍会阻塞请求的返回。 Gevent 让我们能够立即返回并在进程返回后执行 GC。

首先,在您的 wsgi 文件中确保使用 gevent 魔法内容修补全部并禁用垃圾回收。 您可以设置 gc.disable(),但某些库具有上下文管理器,在禁用它后将其打开(例如 messagepack),因此 0 阈值更加粘性。

import gc
from gevent import monkey

# Disable garbage collection runs
gc.set_threshold(0)
# Apply gevent monkey magic
monkey.patch_all()

然后像这样为Django创建一些中间件:

from gc import collect
import gevent

class BaseMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response


class GcCollectMiddleware(BaseMiddleware):
    """Middleware which performs a non-blocking gc.collect()"""

    def __call__(self, request):
        response = self.get_response(request)
        gevent.spawn(collect)
        return response

您会发现与先前建议的方法相比,主要区别在于gc.collect()被包裹在gevent.spawn中,这将不会阻止返回HttpResponse,从而使您的用户获得更快的响应!


我还应该注意,当将中间件添加到您的设置文件中时,请确保它是最后一个中间件。 - Induane

0
我的视图以 return HttpResponse() 结束,之后我想运行 gen 2 GC 扫描。
// turn off GC
// do stuff
resp = HttpResponse()
// turn on GC
return resp

我不确定,但是你可以尝试使用// spawn thread to turn on GC in 0.1 sec代替//turn on GC

为了确保GC在请求处理之后才发生,如果线程创建不起作用,你需要修改django本身或使用某种django钩子,正如dcurtis所建议的那样。

如果你正在处理性能关键代码,你可能还想考虑使用像C/C++这样的手动内存管理语言来处理该部分,并仅使用Python来调用/查询它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接