最近接手一个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 架构图
![59dd895a9b523c964f2a813512ab9b18.png](https://i-blog.csdnimg.cn/blog_migrate/3201ecd162ec394eaf7a314b815f11c3.png)
问题排查思路:
手动验证了下 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
# 或者新启动worker
celery -A tasks worker --loglevel=info -Q celery
问题解决。后续会继续 Celery 的学习,有需要的同学欢迎关注~