在Flask中使用Celery的最佳实践

写在前面

本最佳实践是基于作者有限的经验,欢迎大家共同讨论,可以持续维护此最佳实践。另本文中所使用的环境为Mac&Ubuntu环境,软件版本如下:

  • Celery (4.1.0)
  • Flask (0.12.1)
  • RabbitMQ(3.6.9)
  • librabbitmq (1.6.1)

介绍

简单来说Celery是一个异步的任务队列,当我们需要将一些任务(比如一些需要长时间操作的任务)异步操作的时候,这时候Celery就可以帮到我们,另外Celery还支持定时任务(类似Crontab)。详细的介绍可以参考官网

使用RabbitMQ作为Broker

RabbitMQ是官方推荐使用的Broker,它实际是一个消息中间件,负责消息的路由分发,安装RabbitMQ如下:

# install on Ubuntu
apt-get update
apt-get install rabbitmq-server -yq

需要注意的是,线上环境我们需要创建新的账号,并将guest账号删除,操作如下:

rabbitmqctl add_user myuser mypassword  # 新增用户
rabbitmqctl add_vhost myvhost  # 新增vhost,以使用不同的命名空间
rabbitmqctl set_permissions -p myvhost myuser ".*" ".*" ".*"  # 设置权限
rabbitmqctl  delete_user guest  # 安全原因,删除guest

注意:vhost是一个虚拟空间,用于区分不同类型的消息
然后,在Celery的配置中配置broker URL

CELERY_BROKER_URL = 'amqp://myuser:mypassword@localhost:5672/myvhost'

注意:当使用amqp协议头时,如果安装有librabbitmq则使用librabbitmq,否则使用pyamqp

Celery的日志输出

在task中想要输出日志,最好的方法是通过如下方式

from celery.utils.log import get_task_logger

lg = get_task_logger(__name__)

@celery.task
def log_test():
    lg.debug("in log_test()")

但是仅如此会发现所有的日志最后都跑到shell窗口的stdout当中,原来必须得在启动celery的时候使用-f option来指定输出文件,如下:

celery -A main.celery worker -l debug -f log/celery/celery_task.log &

-A:指定celery实例
worker: 启动worker进程
-l:指定log level,这里指定log level为debug level
-f:指定输出的日志文件

使用Redis作为backend

当使用Redis作为存储后端的时候,我们可以通过设置DB number来使得Celery的结果存储与其它数据存储隔离开来,比如在笔者的项目中,redis还用作缓存的存储后端,因此为了区分,Celery在使用Redis的时候使用的DB number是1(默认是0),关于Redis DB number可以参考这里.
因此我们的backend设置如下:

CELERY_RESULT_BACKEND = 'redis://localhost:6379/1' # 最后的数字1代表DB number

查看Celery任务的结果可以通过Redis-cli连接Redis数据库进行查看

> redis-cli
> select 1 # 这里选择DB 1, 也可以在使用redis-cli -n 1来进入指定的DB
> get key # 获取指定key对应的结果
redis-cli.jpg

调试代码

我认为此处是非常重要的一个技巧,即在调试代码的时候,我们可以将delay或者apply_async先去掉,直接调用worker的函数进行同步调试,调试成功后再加上delay或者apply_async method

Celery可能会遇到的坑

Celery4.x版本使用librabbitmq的问题

Celery 4.x版本在使用librabbitmq时,会出现类似这样的错误

Received and deleted unknown message.  Wrong destination?!?

完整错误如图:


celeryerror

解决这个问题有两个方式:

  1. 推荐方式,更改配置项task_protocol为1。
    Github上Robert Kopaczewski详细解释了这个问题,原文如下:

Apparently librabbitmq issue is related to new default protocol in celery 4.x. You can switch to previous protocol version by either putting CELERY_TASK_PROTOCOL = 1 in your settings if you're using Django or settings app.conf.task_protocol = 1 in celeryconf.py.

  1. 另一种方式是不使用librabbitmq, 通过pip uninstall librabbitmq, 并且更改broker配置的协议头为'pyamqp',如下,也可以解决这个问题。
BROKER_URL = 'pyamqp://guest:guest@localhost:5672/%2F'

由于librabbitmq的性能优势,我们还是推荐方式1来解决该问题。

RabbitMQ远程连接问题

如果RabbitMQ与Celery不在同一台机器上,除在Celery配置的时候要将BROKER_URL设置为正确的IP地址外,还需要将Rabbitmq的配置文件/usr/local/etc/rabbitmq/rabbitmq-env.conf中的NODE_IP_ADDRESS更改为0.0.0.0

NODE_IP_ADDRESS=0.0.0.0

Celery import问题

The message has been ignored and discarded.

Did you remember to import the module containing this task?
Or maybe you're using relative imports?

Please see
http://docs.celeryq.org/en/latest/internals/protocol.html
for more information.

The full contents of the message body was:
'\x8e\xa7expires\xc0\xa3utc\xc3\xa4args\x91\x85\xa3tid\xb85971a43d47f84bb278f77fc2\xa3sen\xa2A1\xa2tt\xa2ar\xa2co\xc4\x00\xa1t\xa4like\xa5chord\xc0\xa9callbacks\xc0\xa8errbacks\xc0\xa7taskset\xc0\xa2id\xc4$c133dbf8-2c89-4311-b7cf-c377041058ec\xa7retries\x00\xa4task\xd9$tasks.messageTasks.send_like_message\xa5group\xc0\xa9timelimit\x92\xc0\xc0\xa3eta\xc0\xa6kwargs\x80' (239b)
Traceback (most recent call last):
  File "/Users/liufeng/.pyenv/versions/2.7.13/envs/kaopu_backend/lib/python2.7/site-packages/celery/worker/consumer/consumer.py", line 561, in on_task_received
    strategy = strategies[type_]
KeyError: u'tasks.messageTasks.send_like_message'

出现这条错误是由于我们的tasks跟celery并不是在同一个文件中,即不是同一个module,当我们通过如下命令启动task worker时,实际只加载了app module,而没有加载tasks相关的module

celery -A app.celery worker -l info

要解决这个问题,必须为celery配置文件添加import参数,如下

app.config['imports'] = ['tasks.messageTasks']

Celery unregistered task问题

在开发过程中遇到了这样一个问题

[2017-08-31 15:38:19,605: ERROR/MainProcess] Received unregistered task of type u'app.tasks.messageTasks.send_follow_message'.
The message has been ignored and discarded.

Did you remember to import the module containing this task?
Or maybe you're using relative imports?

Please see
http://docs.celeryq.org/en/latest/internals/protocol.html
for more information.

The full contents of the message body was:
'\x8e\xa7expires\xc0\xa3utc\xc3\xa4args\x91\x86\xa6sender\xa5Jenny\xa9target_id\xb859a5313847f84be534ad7d46\xabtarget_type\xa4user\xa7content\xc4\x00\xa8receiver\xb859a5313847f84be534ad7d46\xa4type\xa6follow\xa5chord\xc0\xa9callbacks\xc0\xa8errbacks\xc0\xa7taskset\xc0\xa2id\xc4$a4d40c14-1976-41a6-a753-d2a495929920\xa7retries\x00\xa4task\xd9*app.tasks.messageTasks.send_follow_message\xa5group\xc0\xa9timelimit\x92\xc0\xc0\xa3eta\xc0\xa6kwargs\x80' (312b)
Traceback (most recent call last):
  File "/Users/liufeng/.pyenv/versions/2.7.13/envs/kaopu_backend/lib/python2.7/site-packages/celery/worker/consumer/consumer.py", line 561, in on_task_received
    strategy = strategies[type_]
KeyError: u'app.tasks.messageTasks.send_follow_message'

解决这个问题,最开始是根据提示,将所有涉及到task的module全部加上from __future__ import absolute_import 之后运行之后还是不行,后来发现是由于之前启动时使用的是app module, 但是我的代码已经改成了main.py,所以重新启动了celery,最后问题解决

使用镜像迁移系统也依然需要重新添加rabbitmq的用户

问题最开始是发现无法点赞,也无法Follow用户,通过http消息发现出现502错误,于是登录到服务器检查,发现应用服务本身没有任何报错,于是又去查看Celery的日志,结果发现出现如下错误:

[2017-11-13 16:32:01,243: ERROR/MainProcess] consumer: Cannot connect to amqp://celeryuser:**@loc      alhost:5672/celeryvhost: Couldn't log in: a socket error occurred.

经过一番搜索发现网上的评论主要是说URL不对的情况下会出现这种情况,但是我的URL没有改过啊,那又会是什么问题呢?继续看,发现有人提到了权限问题,于是又是一番检查,发现RabbitMQ中并没有原先设置的用户(我使用的是原系统的镜像,原以为用户也是已经设置好的)

# 查看有哪些用户
rabbitmqctl  list_users

然后就简单了,按照步骤创建用户,vhost,再赋予权限,删除guest,然后就终于都连好了

另外,发现从镜像复制系统后,RabbitMQ并不能正常工作,必须杀掉原先的进程,重新启动

更改task的代码后,重启Celery

需要注意的是,在更改task的代码后,必须重新启动Celery,否则代码改动无法生效,可能导致一些意外的问题

使用--pidfile

--pidfile: 可以指定pid,从而避免当出现多个进程的时候Celery不知道该将任务发送给谁,从而出现无法收到任务的情况。所以,我们在启动celery的时候就应当指定pidfile,如下:

--pidfile /var/pidfile/celery.pid
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容