最近接手一个Python web项目,项目中使用 Celery 异步执行一些耗时任务,服务每天都有部分接口阶段性的出现 500 响应。
查看日志发现是 Celery 在执行异步任务时与 Redis 断开连接。
主要错误日志如下:
File ""/usr/local/lib/python3. 6/site-packages/redis/connection, py"", line 613, in send_packed_command (errno, errmsg))redis. exceptions. ConnectionError: Error 104 while writing to socket. Connection reset by peer.
由于刚接手项目,本人对 Celery 也不熟,处于会跑示例代码的水平,所以甩锅给 Redis ,联系运维人员查看 Redis 运行日志,发现如下问题:
Client id=1001 addr=127.0.0.1:49148 fd=17 name= age=3 idle=0 flags=N db=1 sub=559686 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=2998 omem=49146289 events=rw cmd=subscribe scheduled to be closed ASAP for overcoming of output buffer limits.
得出的结论为:
生产者生产任务的速度大于消费者消费任务的速度,导致消息队列中有大量消息堆积,Redis 缓冲区溢出,与 Celery 断开连接。
看来是冤枉运维小哥了,还是找一下自身项目的问题吧。
有关 Redis 缓冲区溢出的讲解可以参考这篇文章,调大缓冲区大小治标不治本
https://www.escapelife.site/posts/ca306858.html
Celery 架构图
![c08802bc8fbdb84848dd8ac1919f7970.png](https://i-blog.csdnimg.cn/blog_migrate/a0c6326b6d2bb4bdeda18d4f0dc0ebaa.jpeg)
问题排查思路:
手动验证了下 task 的执行时间为毫秒级,而且起了 80 个 worker ,所以正常情况下不应该存在消息堆积问题。
问题就出现在不正常的情况下,Celery 在 Redis 中的默认队列名为 celery,登录 Redis 查看该队列中有6w多条消息,观察一段时间后发现消息数量不变,但是查看 worker 日志确实有任务在处理,那是什么原因导致消息未被消费呢?
查看 Celery 的配置文件中启用了路由任务,同时还有定时任务。
# 示例代码,非业务代码# 路由任务CELERY_ROUTES = {'feed.tasks.import_feed': {'queue': 'feeds'}}# 定时任务app.conf.beat_schedule = { 'add-every-30-seconds': { 'task': 'tasks.add', 'schedule': 30.0, 'args': (16, 16) },}
路由任务的作用是:
在使用 delay() 方法或 apply_async() 方法调用 import_feed 任务时会被放入 feeds 队列中
定时任务:
每 30s 执行一次 add 任务
上面提到是默认队列 celery 中有消息堆积,但是配置文件中为每个 task 都指定了其他队列,那这个默认队列中堆积的是啥?
带着疑问进去到默认队列中查看堆积的是什么消息
{'body': 'W1sxLCAxXSwge30sIHsiY2FsbGJhY2tzIjogbnVsbCwgImVycmJhY2tzIjogbnVsbCwgImNoYWluIjogbnVsbCwgImNob3JkIjogbnVsbH1d', 'content-encoding': 'utf-8', 'content-type': 'application/json', 'headers': {'lang': 'py', 'task': 'tasks.add', 'id': '50909224-cccd-49eb-8c97-54df439d121c', 'shadow': None, 'eta': None, 'expires': None, 'group': None, 'retries': 0, 'timelimit': [None, None], 'root_id': '50909224-cccd-49eb-8c97-54df439d121c', 'parent_id': None, 'argsrepr': '(1, 1)', 'kwargsrepr': '{}', 'origin': 'gen34760@U18-4'}, 'properties': {'correlation_id': '50909224-cccd-49eb-8c97-54df439d121c', 'reply_to': '60292f07-395d-3706-a51a-e9ec966a282b', 'delivery_mode': 2, 'delivery_info': {'exchange': '', 'routing_key': 'celery'}, 'priority': 0, 'body_encoding': 'base64', 'delivery_tag': 'c1dce9d0-fcbc-413f-a060-41afb9f05cf7'}}
其中 headers 的 task 记录的为定时任务中的 task 名称。所以结论是定时任务进入到默认队列中并且未被消费。
现在搞清楚是什么任务没有被消费,接下来要查明未被消费的原因。是默认队列优先级不够?还是worker 处理速度不行?
查看 yaml 文件中启动 worker 的命令
celery -A tasks worker --loglevel=info -Q feeds
上面的路由任务中 import_feed 的指定队列为 feeds 所以在启动 worker 时要专门指定 feeds 队列。结果是本次启动 worker 只用来消费 feeds 队列中的消息。并没有 worker 去消费默认队列中的消息,而定时任务就是被放在了默认队列中。
根本问题:
定时任务定时在默认消息队列中新增消息,没有 worker 去消费默认队列中的消息,导致队列中的消息越来越多。
解决方法:
为默认队列启动 worker 来消费消息
# 启动worker同时处理 feeds、celery中的消息celery -A tasks worker --loglevel=info -Q feeds,celery# 或者新启动workercelery -A tasks worker --loglevel=info -Q celery
问题解决。后续会继续 Celery 的学习,有需要的同学欢迎关注~