前情提要
一直以来,我们在 Python 项目中的后台任务都是使用 celery 搭配 Redis(作为 broker)来完成,同时针对短任务轮询场景我们也做了一些封装。在项目运行的三~四年间,这套方案完美地承载了我们核心功能。
然而,就在不久前的一周,出现一些比较诡异的问题,总是有些后台任务发生阻塞,我们使用的多种异常观测手段(Sentry、日志等)都无法准确定位到具体问题(这或许是另一个故事),于是死马当活马医,我们决定将 Redis 更换为 RabbitMQ,这样能够更为准确地观测到任务具体执行的消息情况(例如是否及时Ack)。
案发现场
更换完 broker 之后,却发现了另一个奇怪的问题(没错,这才是本文的主角)。我们在某一些特殊资源的场景下,celery 任务会直接报错:
代码语言:javascript复制ConnectionResetError: [Errno 104] Connection reset by peer
File "kombu/connection.py", line 414, in _reraise_as_library_errors
yield
File "kombu/connection.py", line 494, in _ensured
return fun(*args, **kwargs)
File "kombu/messaging.py", line 203, in _publish
mandatory=mandatory, immediate=immediate,
File "amqp/channel.py", line 1766, in _basic_publish
(0, exchange, routing_key, mandatory, immediate), msg
File "amqp/abstract_channel.py", line 59, in send_method
conn.frame_writer(1, self.channel_id, sig, args, content)
File "amqp/method_framing.py", line 154, in write_frame
2, channel, framelen, frame, 0xce))
File "amqp/transport.py", line 305, in write
self._write(s)
由于我们使用了一层 CLB 作为高可用代理,而之前的使用经验中,CLB 可能会有一些长时间无数据断连的情况,所以我们暂时认为可能是某些长时间的阻塞任务会导致 CLB 主动断开,为了排除干扰,我们甩开了 CLB,直接采用 MQ 的多个节点作为地址直连。
然而,问题依旧,一时间又没了头绪,我开始漫无目的重新浏览 Sentry 中的错误堆栈以及相关变量。
蛛丝马迹
无意间,发现在代码中,我们尝试向队列中存储一大段 pickle 过的对象数据,而这些变量在 Sentry 中已经长到无法完整显示而被省略了。
代码语言:javascript复制...
any_task.apply_async(headers=something_big)
...
这个问题立马引起了我们的注意