具有多处理功能的Celery并行分布式任务


79

我有一个CPU密集的Celery任务。我想使用许多EC2实例上的所有处理能力(核心)来更快地完成此工作我认为是具有多处理功能的芹菜并行分布式任务)

我想更好地理解所有术语,线程多处理分布式计算分布式并行处理

示例任务:

  @app.task
  for item in list_of_millions_of_ids:
      id = item # do some long complicated equation here very CPU heavy!!!!!!! 
      database.objects(newid=id).save()

使用上面的代码(如果可能,还提供一个示例),如何通过Celery使用一项计算任务来拆分这项任务?


我认为MapReduce是为您的应用程序类型而设计的:console.aws.amazon.com/elasticmapreduce/vnext/…
AStopher 2014年

Answers:


119

您的目标是:

  1. 将您的工作分配到许多机器(分布式计算/分布式并行处理)
  2. 将工作分配到所有CPU上的给定计算机上(多处理/线程)

芹菜可以很轻松地为您做这两个。首先要了解的是,每个celery worker默认配置为运行与系统上可用CPU内核数量一样多的任务:

并发是用于同时处理您的任务的前叉工作进程的数量,当所有这些工作都忙于工作时,新任务将必须等待其中一项任务完成才能被处理。

缺省的并发数是该机器(包括核心)上的CPU数,您可以使用-c选项指定自定义数。没有建议值,因为最佳数量取决于许多因素,但是如果您的任务主要是受I / O约束的,那么您可以尝试增加它,实验表明,很少会增加两倍以上的CPU数量有效,并且有可能降低性能。

这意味着每个任务都不必担心使用多处理/线程来利用多个CPU /内核。相反,芹菜将同时运行足够的任务以使用每个可用的CPU。

有了这一点,下一步就是创建一个任务来处理您的的某些子集list_of_millions_of_ids。您这里有两个选择-一个是让每个任务处理一个ID,因此您要运行N个任务,其中N == len(list_of_millions_of_ids)。这将确保工作在所有任务中平均分配,因为永远不会出现一个工人提前完成而只是在等待的情况。如果需要工作,可以将ID移出队列。您可以使用芹菜来做到这一点(如John Doe所述)group

task.py:

@app.task
def process_id(item):
    id = item #long complicated equation here
    database.objects(newid=id).save()

并执行任务:

from celery import group
from tasks import process_id

jobs = group(process_id.s(item) for item in list_of_millions_of_ids)
result = jobs.apply_async()

另一种选择是将列表分成较小的部分,然后将这些部分分发给您的工作人员。这种方法冒着浪费一些周期的风险,因为您可能最终会导致一些工人在等待而其他人仍在工作。但是,芹菜文档指出这种担心通常是没有根据的:

有些人可能会担心将任务分块会导致并行度降低,但是对于繁忙的集群却很少如此,实际上,由于避免了消息传递的开销,这可能会大大提高性能。

因此,由于减少了消息传递开销,您可能会发现对列表进行分块并将分块分配给每个任务的效果更好。您可能还可以通过以下方式来减轻数据库的负担:计算每个ID,将其存储在列表中,然后在完成后将整个列表添加到DB中,而不是一次执行一个ID 。分块方法看起来像这样

task.py:

@app.task
def process_ids(items):
    for item in items:
        id = item #long complicated equation here
        database.objects(newid=id).save() # Still adding one id at a time, but you don't have to.

并开始任务:

from tasks import process_ids

jobs = process_ids.chunks(list_of_millions_of_ids, 30) # break the list into 30 chunks. Experiment with what number works best here.
jobs.apply_async()

您可以尝试一下分块大小可以为您带来最佳结果的方法。您希望找到一个最佳的位置,在其中减少消息传递的开销,同时又要保持足够小的大小,以免最终导致工作人员完成工作的速度比另一个工作人员快得多,然后无所事事地等待着。


因此,我执行“复杂的CPU繁重任务(可能是3d渲染)”的部分将自动进行并行处理,即1个任务将使用所有实例可用的尽可能多的处理能力-以及所有这些盒子?真?哇。PS好的答案,谢谢您向我更好地解释。
Prometheus 2014年

3
@Spike不太好。当前编写的任务只能使用一个核心。为了使单个任务使用多个核心,我们将引入threadingmultiprocessing。取而代之的是,我们让每位芹菜工人产生的任务数量与机器上可用核的数量一样多(这在celery中默认发生)。这意味着在整个集群中,list_of_million_ids每个任务都可以利用一个核心来使用,每个核心都可以用来处理您的。因此,与让单个任务使用多个内核不同,我们有许多任务每个都使用一个内核。那有意义吗?
dano 2014年

1
“要使一个单独的任务使用多个核心,我们将引入threadingmultiprocessing”。假设我们无法将繁重的任务拆分为多个任务,您将如何使用线程化或多处理来使celery在多个实例之间拆分任务?谢谢
Tristan 2015年

@Tristan取决于任务实际执行的操作。但是,在大多数情况下,我想说的是,如果您不能将任务本身拆分为子任务,则可能很难multiprocessing在工作中从任务本身内部进行拆分,因为两种方法最终都需要执行同样的事情:将一个任务拆分为多个较小的任务,这些任务可以并行运行。您实际上只是在更改拆分点。
dano 2015年

1
@PirateApp这个问题是说您不能multiprocessing Celery任务中使用。Celery本身正在使用billiardmultiprocessingfork)在单独的进程中运行任务。只是不允许您在multiprocessing其中使用它们。
dano

12

在发行世界中,您首先应该记住的一件事是:

过早的优化是万恶之源。由D.Knuth

我知道这听起来很明显,但是在分发仔细检查之前,您正在使用最佳算法(如果存在)。话虽如此,优化分配是三件事之间的平衡:

  1. 从持久性介质写入/读取数据,
  2. 将数据从介质A移动到介质B,
  3. 处理数据

制造计算机的目的是使您离处理单元(3)越近,它(1)和(2)就会越快,越高效。经典集群中的顺序为:网络硬盘,本地硬盘,RAM,内部处理单元区域...如今,处理器已变得足够复杂,可以看作是独立硬件处理单元的集合,通常称为核心,这些核心处理数据(3)通过线程(2)。想象一下,您的核心是如此之快,以至于当您使用一个线程发送数据时,您正在使用50%的计算机功能,如果核心具有2个线程,那么您将使用100%。每个内核有两个线程称为超线程,并且您的操作系统将为每个超线程内核看到2个CPU。

在处理器中管理线程通常称为多线程。从OS管理CPU通常称为多处理。在集群中管理并发任务通常称为并行编程。在集群中管理相关任务通常称为分布式编程。

那么您的瓶颈在哪里?

  • 在(1)中:尝试保留并从上层进行流式传输(例如,靠近处理单元的那一层,例如,如果网络硬盘驱动器速度慢,请先保存在本地硬盘驱动器中)
  • 在(2)中:这是最常见的一种,请尝试避免分发不需要的通信数据包或压缩“即时”数据包(例如,如果HD较慢,则仅保存“批量计算”消息并保持中间结果在RAM中)。
  • 在(3)中:完成!您正在使用所有处理能力。

芹菜呢?

Celery是用于分布式编程的消息传递框架,它将使用代理模块进行通信(2)和后端模块进行持久性(1),这意味着您可以通过更改配置来避免大多数瓶颈(如果可能)。您的网络,并且仅在您的网络上。首先配置您的代码,以在一台计算机上获得最佳性能。然后以默认配置在群集中使用celery并进行设置CELERY_RESULT_PERSISTENT=True

from celery import Celery

app = Celery('tasks', 
             broker='amqp://guest@localhost//',
             backend='redis://localhost')

@app.task
def process_id(all_the_data_parameters_needed_to_process_in_this_computer):
    #code that does stuff
    return result

在执行过程中,打开您喜欢的监视工具,我将默认值用于RabbitMQ,将花朵用于芹菜,将top用于cpus,结果将保存在您的后端。网络瓶颈的一个例子是任务队列增长太多,以至于它们延迟了执行,如果没有瓶颈,您可以继续更改模块或celery配置。


9

为什么不group为此使用芹菜任务?

http://celery.readthedocs.org/en/latest/userguide/canvas.html#groups

基本上,您应该将其划分ids为多个块(或范围),并将它们分配给中的一堆任务group

为了更复杂,例如汇总特定芹菜任务的结果,我已经成功地将chord任务用于类似目的:

http://celery.readthedocs.org/en/latest/userguide/canvas.html#chords

增加settings.CELERYD_CONCURRENCY的数量是合理的,你能负担得起,那么这些工人芹菜将继续执行群组或弦在您的任务,直至完成。

注意:由于kombu存在一个错误,过去在重用工作人员执行大量任务时遇到麻烦,所以我不知道它是否已解决。也许可以,但是如果不能,请减少CELERYD_MAX_TASKS_PER_CHILD。

基于我运行的简化和修改代码的示例:

@app.task
def do_matches():
    match_data = ...
    result = chord(single_batch_processor.s(m) for m in match_data)(summarize.s())

summarize获取所有single_batch_processor任务的结果。每个任务都在任何Celery工人上运行,并对其进行kombu协调。

现在我明白了:single_batch_processorsummarize还必须芹菜的任务,而不是常规的功能-当然,否则它不会被并行化(我甚至不能确定和弦的构造会接受它,如果它不是一个芹菜任务)。


根据我的理解,这可以拆分任务,但不会将芹菜并行分布式任务与多处理一起使用。即仅在所有云计算机上使用所有可用的CPU能力。
普罗米修斯

我不确定为什么会发生这种情况-Celery的工作方式就像您有一堆工人一样,无论他们位于何处,他们甚至都可以位于另一台机器上。当然,您需要有多个工人。chord(将CELERYD_CONCURRENCY设置为数十个worker ==逻辑cpus /硬件线程)是我如何在多个内核上以并行方式处理大量日志文件批处理。
LetMeSOThat4U 2014年

这是一个非常糟糕的代码示例。do_matches等待和弦将阻止任务。这可能会导致部分或全部死锁,因为许多/所有工作人员可能都在等待子任务,而这些子任务都不会完成(因为工作人员在等待子任务而不是努力工作)。
Prisacari Dmitrii

@PrisacariDmitrii那么,什么是正确的解决方案呢?
LetMeSOThat4U

4

增加更多的芹菜工作者肯定会加快执行任务的速度。但是,您可能还有另一个瓶颈:数据库。确保它可以处理同时插入/更新。

关于您的问题:您正在通过在EC2实例上将另一个进程分配为来添加celery worker celeryd。您可能需要添加更多实例,这取决于您需要多少工作人员。


>增加更多的芹菜工作者肯定会加快执行任务的速度。 - - 可以?因此,您所说的芹菜将在我所有实例中分配一项任务,而无需我进行拆分?
Prometheus 2014年

等一下。我只是再次阅读了您的代码,由于它只是一项任务,因此无济于事。您可以为每个ID(或ID块)触发一个任务。或者您在另一个答案中遵循John Doe的建议。然后,您可以从芹菜工人的数量中受益。是的,在这种情况下,您不需要做很多事情。只要确保工人使用相同的队列即可。
Torsten Engelbrecht 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.