我正在研究使用芹菜(3.1.8)来处理每个文本文件(~30GB)。这些文件采用fastq格式,包含大约118M的“读取”序列,基本上每个都是标题,DNA序列和质量字符串的组合。此外,这些序列来自配对末端测序运行,因此我同时迭代两个文件(通过itertools.izip)。我希望能够做的是将每对读取,发送到队列,并在我们集群中的一台机器上处理它们(不关心哪些)以返回清理后的版本如果清洁需要发生(例如,基于质量)。
我已经建立了芹菜和兔子,我的工作人员按如下方式启动:
celery worker -A tasks --autoreload -Q transient
并配置如下:
from kombu import Queue
BROKER_URL = 'amqp://guest@godel97'
CELERY_RESULT_BACKEND = 'rpc'
CELERY_TASK_SERIALIZER = 'pickle'
CELERY_RESULT_SERIALIZER = 'pickle'
CELERY_ACCEPT_CONTENT=['pickle', 'json']
CELERY_TIMEZONE = 'America/New York'
CELERY_ENABLE_UTC = True
CELERYD_PREFETCH_MULTIPLIER = 500
CELERY_QUEUES = (
Queue('celery', routing_key='celery'),
Queue('transient', routing_key='transient',delivery_mode=1),
)
我选择使用rpc后端和pickle序列化来提高性能,不是 在“瞬态”队列中将任何内容写入磁盘(通过delivery_mode)。
要设置芹菜框架,我首先在64路盒子上启动rabbitmq服务器(3.2.3,Erlang R16B03-1),将日志文件写入fast / tmp磁盘。工作进程(如上所述)在群集上的每个节点(大约34个)上启动,范围从8路到64路SMP,总共688个核心。因此,我有大量可用的CPU供工作人员用于处理队列。
芹菜启动并运行后,我通过ipython笔记本提交作业,如下所示:
files = [foo, bar]
f1 = open(files[0])
f2 = open(files[1])
res = []
count = 0
for r1, r2 in izip(FastqGeneralIterator(f1), FastqGeneralIterator(f2)):
count += 1
res.append(tasks.process_read_pair.s(r1, r2))
if count == 10000:
break
t.stop()
g = group(res)
for task in g.tasks:
task.set(queue="transient")
对于10000对读取,这需要大约1.5秒。然后,我打电话给小组的延迟提交给工人,大约需要20秒,如下所示:
result = g.delay()
使用rabbitmq控制台监控,我发现我做得很好,但不够快。
那么,有没有办法加快速度呢?我的意思是,我希望看到每秒至少处理50,000个读取对,而不是500.在我的芹菜配置中是否有任何明显的缺失?我的工人和兔子日志基本上是空的。会喜欢一些关于如何提升表现的建议。每个单独的读取对也很快处理:
[2014-01-29 13:13:06,352: INFO/Worker-1] tasks.process_read_pair[95ec7f2f-0143-455a-a23b-c032998951b8]: HWI-ST425:143:C04A5ACXX:3:1101:13938:2894 1:N:0:ACAGTG HWI-ST425:143:C04A5ACXX:3:1101:13938:2894 2:N:0:ACAGTG 0.00840497016907 sec
所以到目前为止,我用谷歌搜索了所有我能想到的芹菜,性能,路由,rabbitmq等等。我已经浏览了芹菜网站和文档。如果我无法获得更高的性能,我将不得不放弃这种方法而转向另一种解决方案(基本上将工作分成许多较小的物理文件,并使用多处理或其他方式直接在每个计算节点上处理它们)。但是,如果无法在群集上传播此负载,那将是一种耻辱。此外,这似乎是一个非常优雅的解决方案。
提前感谢您的帮助!
答案 0 :(得分:2)
不是答案,但评论时间太长。
让我们把问题缩小一点......
首先,尝试跳过所有正常的逻辑/消息准备工作,然后使用当前库执行最紧密的发布循环。看看你得到什么率。这将确定您的非队列相关代码是否存在问题。
如果它仍然很慢,请设置一个新的python脚本,但使用amqplib而不是芹菜。我在中端桌面上做有用的工作(和json编码)时,设法让它以超过6000 / s的速度发布,所以我知道它的性能很高。这将确定问题是否与芹菜库有关。 (为了节省您的时间,我已经从我的项目中剪下了以下内容,并希望在简化时不会破坏它...)
from amqplib import client_0_8 as amqp
try:
lConnection = amqp.Connection(
host=###,
userid=###,
password=###,
virtual_host=###,
insist=False)
lChannel = lConnection.channel()
Exchange = ###
for i in range(100000):
lMessage = amqp.Message("~130 bytes of test data..........................................................................................................")
lMessage.properties["delivery_mode"] = 2
lChannel.basic_publish(lMessage, exchange=Exchange)
lChannel.close()
lConnection.close()
except Exception as e:
#Fail
在上述两种方法之间,您应该能够将问题追溯到其中一个队列,库或您的代码。
答案 1 :(得分:2)
重用生产者实例应该可以提高性能:
with app.producer_or_acquire() as producer:
task.apply_async(producer=producer)
此外,任务可能是代理对象,如果是,则必须对每次调用进行评估:
task = task._get_current_object()
使用group
将自动重用生产者,通常是你想要的
像这样循环:
process_read_pair = tasks.process_read_pair.s
g = group(
process_read_pair(r1, r2)
for r1, r2 in islice(
izip(FastGeneralIterator(f1), FastGeneralIterator(f2)), 0, 1000)
)
result = g.delay()
您还可以考虑安装用C编写的librabbitmq
模块。
amqp://
传输将自动使用(如果可用)(或者可以使用librabbitmq://
手动指定:
pip install librabbitmq
使用底层库直接发布消息可能会更快 因为它会绕过芹菜路线助手等等,但我不会 认为它慢得多。如果是这样,Celery肯定有优化的空间, 因为到目前为止我主要关注的是优化消费者方面。
另请注意,您可能希望在同一任务中处理多个DNA对, 因为使用较粗糙的任务粒度可能对CPU /内存缓存等有益, 并且它通常会使并行化饱和,因为这是一种有限的资源。
注意:瞬态队列应为durable=False
答案 2 :(得分:1)
你有一个解决方案是读取是高度可压缩的,所以取代以下
res.append(tasks.process_read_pair.s(r1, r2))
通过
res.append(tasks.process_bytes(zlib.compress(pickle.dumps((r1, r2))),
protocol = pickle.HIGHEST_PROTOCOL),
level=1))
并在另一边调用pickle.loads(zlib.decompress(obj))
。
如果DNA序列长度不够长,它应该赢得一个因素,因为它们不够长,你可以将它们分组,然后转储并压缩。
另一个胜利可能是使用zeroMQ进行传输,如果你还没有。
我不确定process_byte应该是什么
答案 3 :(得分:0)
同样,不是答案,但评论太长。根据下面的Basic's条评论/答案,我使用与我的应用程序相同的交换和路由设置了以下测试:
from amqplib import client_0_8 as amqp
try:
lConnection = amqp.Connection()
lChannel = lConnection.channel()
Exchange = 'celery'
for i in xrange(1000000):
lMessage = amqp.Message("~130 bytes of test data..........................................................................................................")
lMessage.properties["delivery_mode"] = 1
lChannel.basic_publish(lMessage, exchange=Exchange, routing_key='transient')
lChannel.close()
lConnection.close()
except Exception as e:
print e
你可以看到它正在向前摇摆。
我想现在要找出这个与celery内部正在发生的事情之间的区别
答案 4 :(得分:0)
我将amqp添加到我的逻辑中,而且速度很快。 FML。
from amqplib import client_0_8 as amqp
try:
import stopwatch
lConnection = amqp.Connection()
lChannel = lConnection.channel()
Exchange = 'celery'
t = stopwatch.Timer()
files = [foo, bar]
f1 = open(files[0])
f2 = open(files[1])
res = []
count = 0
for r1, r2 in izip(FastqGeneralIterator(f1), FastqGeneralIterator(f2)):
count += 1
#res.append(tasks.process_read_pair.s(args=(r1, r2)))
#lMessage = amqp.Message("~130 bytes of test data..........................................................................................................")
lMessage = amqp.Message(" ".join(r1) + " ".join(r2))
res.append(lMessage)
lMessage.properties["delivery_mode"] = 1
lChannel.basic_publish(lMessage, exchange=Exchange, routing_key='transient')
if count == 1000000:
break
t.stop()
print "added %d tasks in %s" % (count, t)
lChannel.close()
lConnection.close()
except Exception as e:
print e
所以,我做了一个更改,在循环中向芹菜提交异步任务,如下所示:
res.append(tasks.speed.apply_async(args=("FML",), queue="transient"))
速度方法就是这样:
@app.task()
def speed(s):
return s
再次提交我慢的任务!
所以,它似乎没有任何关系:
但是,它与功能的排队有关吗?!?!我很困惑。
答案 5 :(得分:0)
同样,不是答案,而是更多的观察。通过简单地将我的后端从rpc更改为redis,我的吞吐量增加了三倍以上: