Celery与任务队列
Celery是Python中流行的分布式任务队列。所谓分布式任务队列,是一种将任务分发到不同的线程、进程或者机器上去执行的一种机制。
一个任务队列的输入是一系列工作,我们称为任务。worker进程通过持续监控任务队列获取需要执行的任务。
Celery中的客户端和worker进程通过消息代理(也叫消息中间件)使用消息进行通信。
一个Celery系统可以由多个worker和代理组成,以便保证高可用性和水平拓展性。
注:由于资金有限,Celery官方是不支持Windows的,不过实际测试开发中还是可以用Windows来测试的。
Celery的优势
- 简单:使用简单,用户无需进行复杂的配置即可快速的定义一个分布式任务。后面的例子可以看到这一点。
- 高可用:在连接中断或者失败的情况下,worker和客户端有重试机制保证任务得到执行。
- 高效:一个Celery进程能够在一分钟内处理上百万个任务,这是因为使用协程机制可以大大减小资源消耗。
- 灵活:Celery基于一些定义良好的协议实现,几乎每个组件都可以自定义拓展。
Celery的应用场景
- web应用中,当用户触发的一个操作需要较长时间才能执行完成时,为了提升网站的浏览体验,可以把操作转为后台任务交给Celery去异步执行;
- 一些定时任务,可以定期生成任务到任务队列里交给worker去执行;
- 一些可以异步去执行的任务,比如邮件/短信发送,消息推送等;
消息队列的选择
Celery需要一个消息队列用于在客户端和worker之间接收和发送消息。RabbitMQ
和Redis
在Celery里都有完善的支持,其他消息队列比如Amazon SQS
和Zookeeper
目前支持的不是很好。如果不是很介意掉电数据丢失的话,建议可以从Redis
入手,比较通用。
避坑指南:
笔者按照Flask官方的文档尝试了一下在Flask应用中引入异步的任务,在Windows启动worker时是OK的,但是在执行任务时可能会出现如下错误:
Task handler raised error: ValueError('not enough values to unpack (expected 3, got 0)')
这是因为Celery 4.0+
的版本官方不支持Windows, 而默认的并发模式Prefork
是基于Linux实现的,可行的解决方案是使用gevent
或者eventlet
作为execution pool(eventlet
据说有bug,建议使用gevent
),具体操作如下:
pip install gevent
或者在安装celery的使用使用bundle方式一并安装:
pip install celery[gevent]
然后启动worker时指明使用带上-P gevent
参数,例如:
celery -A tasks worker -l Info -P gevent
拓展
并发模式
Celery的worker进程相当于一个管理进程,一般(除了下面即将提到的solo模式)都是通过spawn新的进程或者线程来完成对应的task,worker本身不参与任务的执行。
Celery的并发模式目前支持如下方式:
-
Prefork
(multiprocessing):进程级并发,适用于CPU密集型的任务。由于GIL的原因,python中如果想充分利用多核CPU的能力必须使用多进程。启动worker进程时,我们可以通过-C
参数我们可以指定并发池的进程数,对于Prefork模式,不指定该参数就是按照CPU内核数来设定进程数的。 -
Eventlet
,gevent
(coroutine):使用协程来进行并发,适用于I/O密集型的任务。Eventlet
和gevent
在低层实现上有所不同,并且API上也完全不同。具体使用哪种模式需要视具体情况而定。 -
Thread
(multithreaded):新加入的模式,高并发的情况性能不如协程,笔者目前没找到比较好的适用场景。 -
Solo
(single threaded):在这种模式下,worker进程并不是spawn一个进程或者线程来完成任务的,而是在自己的进程里面完成任务。所有在执行任务的过程中,worker进程处于block状态。这个模式一般在与微服务中使用,比如将worker以容器的形式启动时,通过直接管理启动的worker数量进而控制并发规模会比通过管理并发池更加简单一些。
网友评论