twisted的BUG?

最近在写一个加速服务,基于http协议,采用了twisted框架。测试中均无发现问题,后来部署在实际环境中,却一而再爆发出一些时延很大的问题。加速服务是部署在f5(请求分发器)跟wap农场之间,所有请求都经过加速器才转发到wap农场。监控组的同学发现这个加速服务一上后就减速了,每次请求时延都达到了5秒以上。
百思不得其解,后来无意中在刷新火狐时(将代理设为加速器),发现一瞬间变得很慢,看了一下加速器日志,发现一有304响应时就会timeout,这很奇怪。加速器的客户端是继承了HTTPPageGetter,由它来向后端的web server发起真正的http请求的,试着在dataReceived接口(当socket有数据时会调用此接口),发现HTTPageGetter层已经收到数据,但却迟迟不返回给上层调用,导致最终延迟。
跟了一下HTTPageGetter的代码,发现其在收到所有header字段时会调用handleEndHeaders这个函数,这个函数会的定义如下:

self.status是响应码,它会根据不同的响应码来调用不同的函数,但是它并没有定义handleStatus_304这个函数,因此默认是调用 了self.handleStatusDefault这个函数,这个函数只将self.failed这个成员附为1,视为错误情况,然后不处理。
问题就出在这里,这时候与web server的链接还保持着,但是它并没有将收到的数据返回给上层调用,而是一直阻塞在socket.recv里,等着接收body,但是当没有body时,就会一直等到超时或者web server断掉连接才返回。
知道问题就好改了,直接将handleStatusDefault这个函数的定义改为如下:

即在不存在content-length这个字段(当self.length==None时没有这个字段)或者值为0时直接返回响应。

更新代码,再次上线,很诡异的时候,uclb(f5后面的请求分发进程,进行会话保持)没有出现超时的情况,但监控组的同学依然表示还是一样,监控到的时延还是很大。
既然不是304导致的,那还会是什么问题呢?为什么用手机连到生产环境都没有问题,测试时也没有发现这个问题,偏偏监控时就会有问题呢?难道是监控脚本的问题?
试着让监控的同学对监控脚本发起请求的过程进行抓包,发现问题出在302响应里,每次加速器返回302头部给监控脚本时,监控脚本并不会马上跳转,而是会明显停顿4、5秒。但是监控脚本直接绕过加速器却没有这个问题,将两次抓包对比了下,发现经过加速器时返回的302 body部分不见了,但是还是带有content-length这个字段。看来监控时会有延长的原因找到了,监控脚本收到302时一直在等待接收body,但是加速器却没有返回body,导致每次耗时爆涨。
看来问题还是出在twisted里,果然,twisted在处理302时并不接收body部分(不管有没有content-length字段),直接在接收完头部时断掉与web server的连接,并返回数据给上层调用。看下代码:

原因找到了,这时候不返回body其实对于普通的浏览器来说都是没有问题的,因为它们根据头部里的location字段就可以进行跳转了,但是监控脚本太死板了,一直要等到body回来直到超时才肯乖乖去跳转。这时候也就加入处理302的函数了,在有body的情况下进行处理,以下是我的处理函数:

跟handleStatusDefault是一致的。

再次更新上线,这次监控脚本终于正常了。

后记:
这里不由得有一个思考,为何twisted在处理302跳转时会直接去掉body部分呢?一个猜想就是其实根据头部已经有足够的信息了,忽略掉body可以加快速度,但是这样对于标准来说是否合适?
另外一个问题就是,twisted在接收body数据时为什么不首先判断一下是否存在content-length或者长度是否为0再决定是否要马上返回响应或者是继续等待web server的响应数据呢?我们再回过头来看下它底层的接收函数:

rawDataReceived是在dataReceived被调用时且已经收完头部数据了被调用的,这时候不管是不是存在body,程序也都会一直在等待数据响应直到超时或连接断掉。这难道是twisted认为这种情况应该由开发者自己去处理?

标签: ,
文章分类 Programming, Python

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*