connection reset案例的穿越之旅
事情要从周五晚上说起,好学的朋友在群里问我有没有能够通过框架和项目能对IO有深入学习的。我当时正照例刷着电影解说,感受着逻辑的力量。等看到消息时,已经看到其他朋友热心得给出了神回复:
在我进行了仔细阅读之后,断线一秒钟,之后由衷感叹自己技术不精,没有弄懂问题和回答之间的逻辑关系。于是给出了自己的回复:
通信框架都需要IO知识,服务治理框架、redis和mysql等存储中间件、MQ都有很强的关联。但是一般很少有很强的动力研究的很深。我个人而言,排查生产问题会引出大量想学习的问题。
然后群里简短的介绍了一个案例:
A与B是两个公司的两个服务,A要调用B服务,他们之间可谓万水千山:
A---服务器--F5--交换机1--交换机2--F5--SSL(透传)--F5--交换机--山石防火墙--H3C交换机--思科路由器---专线--网联思科路由器--H3C交换机--思科防火墙--思科交换机--H3C交换机--思科防火墙--F5-SSL-服务器--F5--交换机1--交换机2--F5--SSL--F5--交换机--山石防火墙--H3C交换机--思科路由器---专线--网联思科路由器--H3C交换机--思科防火墙--思科交换机--H3C交换机--思科防火墙--F5--SSL(非透传)--B
咱们简化一下问题:
A请求B,B正常返回结果,但是A反馈收不到。后来B抓包发现在请求还在进行read数据时就收到了connection reset,连接断开。但是并非每笔请求都是如此。而是A有小于30%的概率收不到。而B对接的其他公司却都正常。
在排查日志时发现B收到的请求分成两种,一种是A可以正常收到的,连接是长连接,一种是A不能正常收到的,连接是短连接。但是长连接还是短连接并不是不能正常返回数据的理由。因为数据是分段传输的,每段之间可以灵活采用自己的连接方式,就像传信时,第一段是采用飞鸽传书,第二段是快递员拿到信用快马送到驿站交到仆人手中,第三段是仆人一路小跑将信递交到我手中。
我猛然一惊,这不是穿越剧,而是技术文章。所以放弃长短连接,看看还没有别的线索,终于发现A不能收到的与能收到的相比http header的 X-Forwarded-For 参数中都多出了2个值。
X-Forwarded-For(XFF)是用来识别通过 HTTP 代理或负载均衡方式连接到 Web 服务器的客户端最原始的 IP 地址的 HTTP 请求头字段。在传输过程中,每一个驿站都有可能通过这个字段打上自己的标记。
将 XFF 的值拿给专业人士验证,竟然有人在B服务前面的万水千山之上又加了一座秦岭。更准确的说是加了30%个秦岭。B服务前被加上了一层nginx,且已经灰度了30%的流量。这就对应了有接近30%的请求有问题。也解释了为什么会有短连接,因为nginx在默认不设置时采用短连接。但是还不能解释为什么只有A有问题。
在nginx的日志中发现了lua异常,对于一个lua完全不懂的人果断向百事通大师谷歌求助。但谷歌大师是西域来的,据说见他要付费。看着手上唯一个「顺治通宝」,正犹豫之时,又听人说咱们有个国产美人「度娘」在情报方面也很厉害,甚至慢慢在赶超谷歌大师。
我连忙前去请教,得到答复:lua脚本一旦抛出异常,就会中断处理。这就解释了为什么会发生connection reset。nginx抛出异常中断了与上层SSL客户端的连接。SSL又作为服务端感知到了异常,主动发connection reset中断了自己与上层的连接。
这种异常场景最好能复现,但是不懂lua怎么复现,这次咱们要来个大手笔,找到真正的大师求助。于是问题上报到了「编程一生」用户交流群,立即得到了lua可以在线调试的重要线索,问题得到复现:
原来处理请求和响应的lua脚本,里面打印日志时for循环遍历请求和响应的数组,遍历时认为每一项都是一个字符串,A应用在传输过程中,将http header的 XFF 变成了一个数组,lua中数组(table)不能直接强转为字符串,被判断为异常触发reset。这也解释了为什么只有A有问题,因为只有他在传输过程中将原本的字符串转成了数组。
在nginx日志还有一个支线线索是:access日志中显示请求结果是200(正常返回结果),但是响应的 Content-Length 为0 !就是说响应为空。看到上面我们可以知道B返回了信息给nginx,nginx异常导致被处理后的响应丢失。出现这个问题的实际不是A一个,还有另外一家。本身B服务请求量很小,没有报出来也很正常。
从规律上来说:我怀疑可能是commons-httpclient-3.1-rc1版本以下会采用这种策略,因为从请求日志头中看到出现问题的只有两个httpclient版本过来的请求,另外一个是commons-httpclient-3.0-rc3 版本。这里再顺便介绍一下apache各个类型的版本。
Alpha:
Alpha是内部测试版,表示最初的版本,一般不向外部发布。Alpha版会有很多Bug,除非你想去测试最新的功能,否则一般不建议使用。
Beta:
该版本相对于Alpha版已有了很大的改进,消除了严重的错误,但还是存在着一缺陷,需要经过多次测试来进一步消除。这个阶段的版本会一直加入新的功能。`
RC:(Release Candidate)
Candidate是候选人的意思,用在软件上就是候选版本。Release.Candidate.就是发行候选版本。和Beta版最大的差别在于Beta阶段会一直加入新的功能,但是到了RC版本,几乎就不会加入新的功能了,而主要着重于除错! RC版本是最终发放给用户的最接近正式版的版本,发行后改正bug就是正式版了,就是正式版之前的最后一个测试版。`
GA:(general availability)
比如:Apache Struts 2 GA这是Apache Struts 2首次发行稳定的版本,GA意味着General Availability,也就是官方开始推荐广泛使用了。
Release:
该版本意味“最终版本”,在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本。该版本有时也称为标准版。一般情况下,Release不会以单词形式出现在软件封面上,取而代之的是符号(R)。
从案例来看,用过老的或者不用稳定版本,当了别人的小白鼠,可以获得很多技术精进的机会。因为你会踩很多其他开发者没有踩过的坑。同时还可以获得头发护理的永久免费: