celery执行池

celery执行池

参考:

Celery Execution Pools: What is it all about?

celery worker

使用celery --app=时,相当于启动了一个监督进程(supervisor process),worker自身不执行任何任务。它会产生一些子进程(或线程),这些进程(或线程)执行实际的任务。这些子进程(或线程)也被称为执行池(execution pool)。

执行池的大小决定了celery worker可以处理的任务数目。产生的进程(或线程)越多,可以并行处理的任务越多。

理论上来说,如果想尽快处理多个任务,就需要更大的执行池。但实际上的情况会复杂一些。执行池的大小取决于使用的是进程还是线程,而使用进程还是线程取决于要做的任务。

-pool选项

pool选项可选择使用进程还是线程。如下面的命令创建了100个协程

# start celery worker with the gevent pool
~$ celery worker --app=worker.app --pool=gevent --concurrency=100

celery提供了四种执行池的实现:

  • prefork(不指定的话默认)
  • solo
  • eventlet
  • gevent

下面将讲解各个实现。

prefork

prefork基于python的multiprocessing包实现。因此可以充分利用机器的多个处理器。

任务是CPU密集型的应用是可选择prefork pool。

如果不指定concurrency参数,将会使用与CPU数量一致的进程数。

# start celery worker with the prefork pool
~$ celery worker --app=worker.app

solo

solo pool是有点特殊的执行池。严格的讲,solo pool不是基于进程或基于线程的。更严格的讲,solo pool违背了worker自身不执行任何任务的原则。

solo pool运行于worker process内。它内联运行,因此solo worker很快。但是它在执行任务时也会阻塞worker,这会对一些远程控制worker造成影响。

# start celery worker in solo mode
~$ celery worker --app=worker.app --pool=solo

eventlet和gevent

celery支持两种基于线程的执行池:eventlet和gevent。这里,执行池在与celery worker自身相同的进程中运行。更精确地说,eventlet和gevent都使用greenlets,而不使用线程。

greenlets也被称为green线程(green threads)、协作线程(cooperative threads)或协程(coroutines)。它可以提供线程的功能,却不使用线程。线程由操作系统内核负责管理,操作系统使用一个通用目的的调度器来在线程间做切换,这种方式通常不是很高效。

协程在不依赖任何原生的操作系统能力的情况下模拟多线程环境。它在用户空间而非内核空间被管理。没有调度器在任何时刻抢先在你的线程间切换,相反,你的协程在你代码的指定点自愿或显式的将控制权交给另一个协程。

这使得协程擅长运行大量的非阻塞任务。

对于我们来说,使用eventlet或gevent pool的好处是使得celery worker可以做更多工作。这意味着我们不需要很多RAM来扩展规模,优化了我们worker的利用率。

# start celery worker using the gevent pool with 500 worker threads
~$ celery worker --app=worker.app --pool=gevent --concurreny=500
# start celery worker using the eventlet pool with 500 worker threads
~$ celery worker --app=worker.app --pool=eventlet --concurreny=500

gevent和eventlet是python的包,它们的实现有差异。

–concurrency选项

为选择最佳的执行池,你需要理解你的任务是CPU密集型还是IO密集型的。CPU密集型的最好使用prefork执行池。I/O密集型的最好使用gevent/eventlet执行池。(solo执行池:我不要面子的吗?)

现在唯一的问题是:你需要启动多少个进程/线程?–concurrency选项决定了进程/线程的数量

# start celery worker using the prefork pool
~$ celery worker --app=worker.app --concurrency=2

上述命令启动了一个有prefork执行池的worker,该worker拥有两个进程。对于prefork池,进程数不应该超过CPU数。

如果--concurrency选项未设置,不管执行池的种类是什么,celery默认将其设置为CPU的数量。这个设定对与prefork执行池来说讲的通,但是它是具有一定误导性的。如果机器上有很多其他的进程,celery的进程数与CPU数量相同就不是一个好主意。

使用默认的concurrency选项对于gevent/eventlet池来说也很蠢。协程数由你决定,与CPU数目无关。

另一个特殊情况是solo pool,尽管可以在命令行提供--concurrency命令,但是它对于solo pool是无意义的。(怪不得项目里用solo pool + concurrency感觉无效)

celery对于CPU密集型和I/O密集型混合任务的处理

建立两个队列,一个队列使用prefork执行池,用于处理CPU密集型任务;另一个队列使用gevent/eventlet执行池,用于处理IO密集型任务。不要忘了路由你的任务到正确的队列。