我正试图在尽可能短的时间内完成数以千计的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个工作服务器,但随着我添加更多服务器,任务完成率开始趋于平稳。
答案 0 :(得分:1)
对于未来的读者。有帮助的行动,最重要的是先受益:
原始评论讨论中未提及更多有用的提示,因此对此问题具有未知的利益意义。
httplib2
或urllib3
或更好的HTTP库。 requests
没有充分理由燃烧CPU 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负载可能是有益的。
此答案中的所有代码仅显示大致方向。您必须使用较少的复制和分配来实现更好的版本。