芹菜工人的利用率随着更多工人的减少而减少

时间:2018-02-05 19:26:16

标签: concurrency rabbitmq celery distributed-computing eventlet

我正试图在尽可能短的时间内完成数以千计的GET请求。我需要以可扩展的方式这样做:将用于发出请求的服务器数量加倍,可以将完成固定数量的URL的时间减半。

我正在使用带有eventlet池的Celery和作为代理的RabbitMQ。我使用--concurrency 100在每个工作服务器上生成一个工作进程,并有一个专用的主服务器发出任务(下面的代码)。我没有得到我期望的结果:当使用的工作服务器数量增加一倍时,完成的时间根本没有减少。

似乎随着我添加更多工作服务器,每个工作程序的利用率下降(如Flower所报告)。例如,对于2名工作人员,在整个执行过程中,每个工作人员的活动线程数在80到90范围内徘徊(正如预期的那样,因为并发性为100)。但是,对于6名工作人员,每个工作人员的活动线程数在10到20范围内徘徊。

这几乎就像队列大小太小,或者工作服务器无法足够快地将任务从队列中拉出来以便充分利用,而当你添加更多工作人员时,他们很难快速将任务从队列中拉出来。

urls = ["https://...", ..., "https://..."]
tasks = []
num = 0
for url in urls:
    num = num + 1
    tasks.append(fetch_url.s(num, url))

job = group(tasks)
start = time.time()
res = job.apply_async()
res.join()
print time.time() - start

更新:我已经附加了使用1个工作服务器,2个工作服务器等最多5个工作服务器的成功任务与时间的图表。正如您所看到的,任务完成率从1个工作服务器增加到2个工作服务器,但随着我添加更多服务器,任务完成率开始趋于平稳。enter image description here

1 个答案:

答案 0 :(得分:1)

对于未来的读者。有帮助的行动,最重要的是先受益:

  • 将几个小型工作单位分成一个芹菜任务
  • 将芹菜经纪人从RabbitMQ转到Redis

原始评论讨论中未提及更多有用的提示,因此对此问题具有未知的利益意义。

  • 使用httplib2urllib3或更好的HTTP库。 requests没有充分理由燃烧CPU
  • 使用HTTP连接池。检查并确保重用与目标服务器的永久连接。

Chunking解释道。

在分块之前

urls = [...]

function task(url)
  response = http_fetch(url)
  return process(response.body)

celery.apply_async url1
celery.apply_async url2
...

因此任务队列包含N = len(urls)任务,每个任务都是获取单个url,对响应执行一些计算。

使用分块

function chunk(xs, n)
  loop:
  g, rest = xs[:n], xs[n:]
  yield g

chunks = [ [url1, url2, url3], [4, 5, 6], ... ]

function task(chunk)
  pool = eventlet.GreenPool()
  result = {
    response.url: process(response)
    for response in pool.imap(http_fetch, chunk)
  }
  return result

celery.apply_async chunk1
celery.apply_async chunk2
...

现在任务队列包含M = len(urls)/ chunksize任务,每个任务是获取chunksize url并处理所有响应。现在,您必须在单个块中复用并发url提取。在这里,它使用了Eventlet GreenPool。

注意,因为Python,首先执行所有网络IO然后对块中的所有响应执行所有CPU计算,通过多个芹菜工作者分摊CPU负载可能是有益的。

此答案中的所有代码仅显示大致方向。您必须使用较少的复制和分配来实现更好的版本。