调整芹菜以获得高性能

时间:2013-09-17 07:54:36

标签: python django performance rabbitmq celery

我正在尝试发送~400个HTTP GET请求并收集结果。 我是从django跑来的。 我的解决方案是将芹菜与gevent一起使用。

要开始芹菜任务,我打电话给 get_reports

def get_reports(self, clients, *args, **kw):
    sub_tasks = []
    for client in clients:  
            s = self.get_report_task.s(self, client, *args, **kw).set(queue='io_bound')
        sub_tasks.append(s)
    res = celery.group(*sub_tasks)()
    reports = res.get(timeout=30, interval=0.001)
    return reports

@celery.task
def get_report_task(self, client, *args, **kw):
    report = send_http_request(...)
    return report

我使用4名工人:

manage celery worker -P gevent --concurrency=100 -n a0 -Q io_bound
manage celery worker -P gevent --concurrency=100 -n a1 -Q io_bound
manage celery worker -P gevent --concurrency=100 -n a2 -Q io_bound
manage celery worker -P gevent --concurrency=100 -n a3 -Q io_bound

我使用RabbitMq作为经纪人。

虽然它比按顺序运行请求的速度快得多(400个请求需要大约23秒),但我注意到大部分时间都是来自芹菜本身的开销,即如果我改变 get_report_task 这样的话:

@celery.task
def get_report_task(self, client, *args, **kw):
    return []
整个操作耗时约19秒。 这意味着我只花了19秒将所有任务发送到芹菜并获得结果

对于兔子mq的消息的排队速率似乎是28个消息/秒,我认为这是我的瓶颈。

如果重要的话,我正在使用win 8机器。

我尝试过的一些事情:

  • 使用redis作为经纪人
  • 使用redis作为结果后端
  • 使用这些设置进行调整

    BROKER_POOL_LIMIT = 500

    CELERYD_PREFETCH_MULTIPLIER = 0

    CELERYD_MAX_TASKS_PER_CHILD = 100

    CELERY_ACKS_LATE = False

    CELERY_DISABLE_RATE_LIMITS = True

我正在寻找任何有助于加快速度的建议。

3 个答案:

答案 0 :(得分:6)

您是否真的在没有虚拟机的Windows 8上运行?我在运行OS X 10.7的2核心Macbook 8GB RAM上进行了以下简单测试:

import celery
from time import time

@celery.task
def test_task(i):
    return i

grp = celery.group(test_task.s(i) for i in range(400))
tic1 = time(); res = grp(); tac1 = time()
print 'queued in', tac1 - tic1
tic2 = time(); vals = res.get(); tac2 = time()
print 'executed in', tac2 - tic2

我使用Redis作为经纪人,Postgres作为结果后端,默认工作人员使用--concurrency=4。猜猜输出是什么?这是:

  

排队在3.5009469986

     

在2.99818301201执行

答案 1 :(得分:2)

好吧,我有两个不同的问题。

首先,该任务是一个成员方法。将它从课堂中提取出来后,时间减少到大约12秒。我只能假设它与 self 的酸洗有关。

第二件事是它在Windows上运行。 在我的linux机器上运行后,运行时间不到2秒。 猜猜窗户不是因为高性能而被削减。

答案 2 :(得分:0)

如何使用扭曲呢?您可以获得更简单的应用程序结构。您可以立即从django进程发送所有400个请求,并等待所有请求完成。这同时工作,因为twisted将套接字设置为非阻塞模式,并仅在可用时读取数据。

前一段时间我遇到了类似的问题,我在twisted和django之间建立了一个很好的桥梁。我现在在生产环境中运行它差不多一年了。你可以在这里找到它:https://github.com/kowalski/featdjango/。简单来说,它有主应用程序线程运行主扭曲反应器循环,并且django视图结果被委托给一个线程。它使用一个特殊的线程池,它公开了与reactor交互的方法并使用它的异步功能。

如果您使用它,您的代码将如下所示:

from twisted.internet import defer
from twisted.web.client import getPage

import threading


def get_reports(self, urls, *args, **kw):
    ct = threading.current_thread()

    defers = list()
    for url in urls:
        # here the Deferred is created which will fire when
        # the call is complete
        d = ct.call_async(getPage, args=[url] + args, kwargs=kw)
        # here we keep it for reference
        defers.append(d)

    # here we create a Deferred which will fire when all the
    # consiting Deferreds are completed
    deferred_list = defer.DeferredList(defers, consumeErrors=True)
    # here we tell the current thread to wait until we are done
    results = ct.wait_for_defer(deferred_list)

    # the results is a list of the form (C{bool} success flag, result)
    # below unpack it
    reports = list()
    for success, result in results:
        if success:
            reports.append(result)
        else:
            # here handle the failure, or just ignore
            pass

    return reports

这仍然是你可以优化的东西。在这里,每次调用getPage()都会创建一个单独的TCP连接,并在完成后关闭它。这是最佳的,只要将400个请求中的每一个发送到不同的主机即可。如果不是这种情况,则可以使用http连接池,该连接池使用持久连接和http管道。你实例化它:

from feat.web import httpclient

pool = httpclient.ConnectionPool(host, port, maximum_connections=3)

单个请求的执行方式与此类似(这取代了getPage()调用):

d = ct.call_async(pool.request, args=(method, path, headers, body))