故事从这里开始
---
在一个平静的夜晚,正当在沙发上坐下来享受短暂的夜间时光时,急促而连续的报警声响了。于是发现 uwsgi 和 rabbitmq 在激烈地纠缠中:
“原话版”:
uwsgi worker-1:Hi rabbitmq,我消息发过来了,快接一下。rabbitmq:Ummm…等会儿等会儿,我有点忙不过来了,排个队。uwsgi worker-1:搞啥啊,再不快点儿包工头就要我命了。rabbitmq:…n 秒之后,uwsgi worker-1 被包工头责令下课…---uwsgi worker-2 启动,连接 rabbitmq…uwsgi worker-2:Hi rabbitmq,我消息发过来了,快接一下。rabbitmq:Ha?又来一个?什么鬼,排队排队。uwsgi worker-2:搞啥啊,再不快点儿包工头就要我命了。rabbitmq:…n 秒之后,uwsgi worker-2 被包工头责令下课…---repeat thousands times…
“译文版”:
uwsgi worker-1:发送 mq 消息rabbitmq:connection blocked 状态,阻塞客户端发送消息uwsgi worker-1:持续等待 recvn 秒之后,触发 harakiri,uwsgi worker-1 被回收---repeat thousands times…
关键字解剖
---
rabbitmq:connection blocked
这是 rabbitmq 的自我保护机制,当 rabbitmq 所在机器内存、磁盘等达到了水位线,即会触发流控甚至完全阻塞消息发布,而带给客户端的即是上面 uwsgi 所遇到的等待(假死)。这里特别注意内存的水位线,默认是总内存的 40% 即触发。
uwsgi:harakiri
这是 uwsgi 所提供的一个功能,相当于为 worker 设置一个请求处理的超时时间,如果 worker 在处理一个请求时达到了设置的 harakiri 时间,则会导致该 worker 被回收(be killed with no mercy),然后启动新的 worker 来顶替。这算是 uwsgi 的保护机制,可以理解为是一种“熔断”。
回到现场
---
从前面的关键字解剖中,其实已经大概知晓了问题发生的原因。具体过程如下:
- rabbitmq 因为各种不当原因,在事故前达到了内存水位线,从而触发了流控;
- uwsgi worker 因为流控而导致部分 worker 的响应延迟飘高,甚至部分达到 harakiri 的阈值而发生回收;
- 大量重启的 worker 持续连接 rabbitmq,进一步加重了 rabbitmq 的负担,使情况进一步恶化;
- 继续恶性循环,且愈演愈烈…
重要的一课
---
其实很多时候,灾难都源于考虑问题的片面化、疏忽以及对工具的不当使用。还是从这里的两个主角说起。
首先是 rabbitmq,它可以很方便的为我们提供功能丰富的消息队列。rabbitmq 很老牌,性能不算太高,但在不算非常大型的应用场景下,它依然是不错的选择,并且在 python third packages 里也对 rabbitmq 的支持很成熟。平常使用,甚至在压测的场景下,我们总是将重心放在了 rabbitmq 的吞吐上,却疏忽了其对于磁盘、内存等的要求与使用的“姿势”。对工具的全方位了解,有时也是非常关键的。
而 uwsgi,对于从事 python web 开发的同学来说一定再熟悉不过。一个不太妥的说法,它算是一个壳儿,而如何与应用正确的结合是非常重要的。
在上面,我们用到了 harakiri 这个功能,意在某些异常情况或接口偶尔响应缓慢的时候加一层保护,以防 worker 卡死导致整体异常。但 harakiri 本身是非常极端的,它会直接 kill worker,这就会导致与 db 等的连接不会正常关闭。如果短时间内出现大量的 worker 被 kill 然后重启,就可能瞬间冲击底层的服务,造成更大的麻烦。
但一般来讲,即使使用 harakiri,其充当的也应仅是最后一道防线,应用理应防止触及这条线。所以反观应用本身,对于可能发生响应抖动的点要重点关注,特别是 I/O 层面的处理,应更做到更细致地考虑。在我们的应用中,对于 rabbitmq 请求超时时间的设置就远大于 harakiri 所设置的时间,固然在异常情况下容易引发问题。
结局
---
Oops,是时候做优化了
好想看看这个Django框架的源码,做的很不错
狠会玩哦
真心不错
测试