过程可以参考文章第三段,这里直接上结论:
1、当发起一个 task 时,会向 redis 中插入以"celery"为key一条列表类型的记录。
2、如果这时有正在待命的空闲 worker,这个 task 会立即被 worker 领取。
3、如果这时没有空闲的 worker,这个 task 的记录会保留在"celery" key 中。
4、如果task被worker领取,这时会将这个 task 的记录从 key celery 中移除,并添加相关信息到 unacked
和 unacked_index
中。
5、worker 根据 task 设定的期望执行时间执行任务,如果接到的不是延时任务或者已经超过了期望时间,则立刻执行。
6、worker 开始执行任务时,通知 redis。(如果设置了 CELERY_ACKS_LATE = True 那么会在任务执行结束时再通知)
7、redis 接到通知后,将 unacked
和 unacked_index
中相关记录移除。
8、如果在接到通知前,worker 中断了(ctrl+c),这时 redis 中的 unacked 和 unacked_index 记录会重新回到 celery
key 中。(如果用kill -9杀掉,那么这个task就如法重新回到celery key中,所以在关闭 worker 时为防止任务丢失,请务必使用正确的方法停止它,如: celery multi stop w1 -A proj1
)
9、celery 只是利用 redis 的 list 类型,当作个简单的 Queue,并没有使用消息订阅等功能
关于定时任务:
celery依赖beat实现定时任务,大致的实现原理是:
beat会根据你设定的期望执行时间不断向redis中推送任务。
比如每小时执行一次定时任务,那么beat将每隔一小时向redis中推送一个任务。然后重复以上几步worker会去获取这些任务并被worker执行。
下面在分析一下celery向redis中推送的消息任务到底长什么样:
celery向任务队列(broker)或结果存储(backend)中推送消息时,会先对数据进行序列化(类似web前端将请求体数据转为json字符串在提交给后台一样)
celery消息序列化的方式包括四种:['pickle', 'json', 'msgpack', 'yaml']
下面以json为例,分析一下消息从发送到被消费的整个过程:
配置文件中指定以json格式序列化消息:
# 指定任务序列化方式 CELERY_TASK_SERIALIZER = 'json' # 指定结果序列化方式 CELERY_RESULT_SERIALIZER = 'json' # 指定任务接受的序列化类型 CELERY_ACCEPT_CONTENT = ['json']
创建一个异步任务:
@shared_task(bind=True, track_started=True) def send_email(self, content): print('开始执行发送邮件异步任务,邮件内容:%s' % content) return {'message': '成功'}
当你触发一个task时(此时先不启动worker,目的是不让worker去队列中认领消息),你的消息队列中可以看到这样一条以json格式序列化的数据:
127.0.0.1:6379> keys *
1) "_kombu.binding.celery"
2) "celery"
127.0.0.1:6379> lrange celery 0 -1
1) "{\"body\": \"eyJ0YXNrIjogInNlbmRfZW1haWwudGFza3Muc2VuZF9lbWFpbCIsICJpZCI6ICJkZGE5YWUxYi01N2QxLTQ4ODQtYTNiNS02OThmZTkyZWZmZTEiLCAiYXJncyI6IFsiXHU2MjExXHU2NjJmXHU5MGFlXHU0ZWY2XHU1MTg1XHU1YmI5Il0sICJrd2FyZ3MiOiB7fSwgInJldHJpZXMiOiAwLCAiZXRhIjogbnVsbCwgImV4cGlyZXMiOiBudWxsLCAidXRjIjogdHJ1ZSwgImNhbGxiYWNrcyI6IG51bGwsICJlcnJiYWNrcyI6IG51bGwsICJ0aW1lbGltaXQiOiBbbnVsbCwgbnVsbF0sICJ0YXNrc2V0IjogbnVsbCwgImNob3JkIjogbnVsbH0=\", \"content-encoding\": \"utf-8\", \"content-type\": \"application/json\", \"headers\": {}, \"properties\": {\"reply_to\": \"0907e016-fc33-37bd-afd2-1b5633f0455f\", \"correlation_id\": \"9d9dde7c-6fb4-4a6e-88f3-619065582455\", \"delivery_mode\": 2, \"delivery_info\": {\"priority\": 0, \"exchange\": \"celery\", \"routing_key\": \"celery\"}, \"body_encoding\": \"base64\", \"delivery_tag\": \"b89af25f-d852-47b9-944f-ecb4671cc3b9\"}}"
127.0.0.1:6379>
json.loads解析以上json数据:
>>>json.loads(
"{\"body\": \"eyJ0YXNrIjogInNlbmRfZW1haWwudGFza3Muc2VuZF9lbWFpbCIsICJpZCI6ICJkZGE5YWUxYi01N2QxLTQ4ODQtYTNiNS02OThmZTkyZWZmZTEiLCAiYXJncyI6IFsiXHU2MjExXHU2NjJmXHU5MGFlXHU0ZWY2XHU1MTg1XHU1YmI5Il0sICJrd2FyZ3MiOiB7fSwgInJldHJpZXMiOiAwLCAiZXRhIjogbnVsbCwgImV4cGlyZXMiOiBudWxsLCAidXRjIjogdHJ1ZSwgImNhbGxiYWNrcyI6IG51bGwsICJlcnJiYWNrcyI6IG51bGwsICJ0aW1lbGltaXQiOiBbbnVsbCwgbnVsbF0sICJ0YXNrc2V0IjogbnVsbCwgImNob3JkIjogbnVsbH0=\", \"content-encoding\": \"utf-8\", \"content-type\": \"application/json\", \"headers\": {}, \"properties\": {\"reply_to\": \"0907e016-fc33-37bd-afd2-1b5633f0455f\", \"correlation_id\": \"9d9dde7c-6fb4-4a6e-88f3-619065582455\", \"delivery_mode\": 2, \"delivery_info\": {\"priority\": 0, \"exchange\": \"celery\", \"routing_key\": \"celery\"}, \"body_encoding\": \"base64\", \"delivery_tag\": \"b89af25f-d852-47b9-944f-ecb4671cc3b9\"}}"
)
结果:
>>>{'body': 'eyJ0YXNrIjogInNlbmRfZW1haWwudGFza3Muc2VuZF9lbWFpbCIsICJpZCI6ICJkZGE5YWUxYi01N2QxLTQ4ODQtYTNiNS02OThmZTkyZWZmZTEiLCAiYXJncyI6IFsiXHU2MjExXHU2NjJmXHU5MGFlXHU0ZWY2XHU1MTg1XHU1YmI5Il0sICJrd2FyZ3MiOiB7fSwgInJldHJpZXMiOiAwLCAiZXRhIjogbnVsbCwgImV4cGlyZXMiOiBudWxsLCAidXRjIjogdHJ1ZSwgImNhbGxiYWNrcyI6IG51bGwsICJlcnJiYWNrcyI6IG51bGwsICJ0aW1lbGltaXQiOiBbbnVsbCwgbnVsbF0sICJ0YXNrc2V0IjogbnVsbCwgImNob3JkIjogbnVsbH0=', 'content-encoding': 'utf-8', 'content-type': 'application/json', 'headers': {}, 'properties': {'reply_to': '0907e016-fc33-37bd-afd2-1b5633f0455f', 'correlation_id': '9d9dde7c-6fb4-4a6e-88f3-619065582455', 'delivery_mode': 2, 'delivery_info': {'priority': 0, 'exchange': 'celery', 'routing_key': 'celery'}, 'body_encoding': 'base64', 'delivery_tag': 'b89af25f-d852-47b9-944f-ecb4671cc3b9'}}
json解析之后,我们再将body中的数据进行base64解码:
>>>base64.b64decode('eyJ0YXNrIjogInNlbmRfZW1haWwudGFza3Muc2VuZF9lbWFpbCIsICJpZCI6ICJkZGE5YWUxYi01N2QxLTQ4ODQtYTNiNS02OThmZTkyZWZmZTEiLCAiYXJncyI6IFsiXHU2MjExXHU2NjJmXHU5MGFlXHU0ZWY2XHU1MTg1XHU1YmI5Il0sICJrd2FyZ3MiOiB7fSwgInJldHJpZXMiOiAwLCAiZXRhIjogbnVsbCwgImV4cGlyZXMiOiBudWxsLCAidXRjIjogdHJ1ZSwgImNhbGxiYWNrcyI6IG51bGwsICJlcnJiYWNrcyI6IG51bGwsICJ0aW1lbGltaXQiOiBbbnVsbCwgbnVsbF0sICJ0YXNrc2V0IjogbnVsbCwgImNob3JkIjogbnVsbH0=')
>>>b'{"task": "send_email.tasks.send_email", "id": "9d9dde7c-6fb4-4a6e-88f3-619065582455", "args": ["\\u6211\\u662f\\u90ae\\u4ef6\\u5185\\u5bb9"], "kwargs": {}, "retries": 0, "eta": null, "expires": null, "utc": true, "callbacks": null, "errbacks": null, "timelimit": [null, null], "taskset": null, "chord": null}'
再将base64解码之后的数据再解码:
>>>b'{"task": "send_email.tasks.send_email", "id": "9d9dde7c-6fb4-4a6e-88f3-619065582455", "args": ["\\u6211\\u662f\\u90ae\\u4ef6\\u5185\\u5bb9"], "kwargs": {}, "retries": 0, "eta": null, "expires": null, "utc": true, "callbacks": null, "errbacks": null, "timelimit": [null, null], "taskset": null, "chord": null}'.decode(encoding='unicode_escape')
>>>'{"task": "send_email.tasks.send_email", "id": "9d9dde7c-6fb4-4a6e-88f3-619065582455", "args": ["我是邮件内容"], "kwargs": {}, "retries": 0, "eta": null, "expires": null, "utc": true, "callbacks": null, "errbacks": null, "timelimit": [null, null], "taskset": null, "chord": null}'
解码之后,就可以看到一个完整的celery消息了,消息中:
此时,启动celery worker,让它消费掉redis中的消息:
可以看到task被执行了,再回到redis,查看任务执行结果:
127.0.0.1:6379> keys *
1) "_kombu.binding.celeryev"
2) "_kombu.binding.celery"
3) "_kombu.binding.celery.pidbox"
4) "celery-task-meta-5b23fae7-82fe-4879-ada5-8e6e292084ee"
5) "unacked_mutex"
127.0.0.1:6379> get "celery-task-meta-5b23fae7-82fe-4879-ada5-8e6e292084ee"
"{\"status\": \"SUCCESS\", \"result\": {\"message\": \"\\u6210\\u529f\"}, \"traceback\": null, \"children\": []}"
127.0.0.1:6379>
以同样的方式对结果进行解析:
>>>json.loads("{\"status\": \"SUCCESS\", \"result\": {\"message\": \"\\u6210\\u529f\"}, \"traceback\": null, \"children\": []}")
>>>{'status': 'SUCCESS', 'result': {'message': '成功'}, 'traceback': None, 'children': []}