Connectionreset原因分析和解决⽅案
最近线上总是在访问静态资源的时候间断性的报错,socket Connection reset,在环境、代码上花了⼤量时间没查个所以然,不得不使⽤强⼤的度娘,最后看了⼀篇开源博客⾥的⽂章写的不错,在这⾥转载下,希望可以帮助更多的童鞋解决这个困惑
⽂章转⾃:my.oschina/xionghui/blog/508758
在使⽤HttpClient调⽤后台resetful服务时,“Connection reset”是⼀个⽐较常见的问题,有同学跟我私信说被这个问题困扰很久了,今天就来分析下,希望能帮到⼤家。例如我们线上的⽹关⽇志就会抛该错误:
从⽇志中可以看到是Socket套接字在read数据时抛出了该错误。
导致“Connection reset”的原因是服务器端因为某种原因关闭了Connection,⽽客户端依然在读写数据,此时服务器会返回复位标
志“RST”,然后此时客户端就会提⽰“java.SocketException: Connection reset”。
nginx和网关怎么配合使用可能有同学对复位标志“RST”还不太了解,这⾥简单解释⼀下:
TCP建⽴连接时需要三次握⼿,在释放连接需要四次挥⼿;例如三次握⼿的过程如下:
1. 第⼀次握⼿:客户端发送syn包(syn=j)到服务器,并进⼊SYN_SENT状态,等待服务器确认;
2. 第⼆次握⼿:服务器收到syn包,并会确认客户的SYN(ack=j+1),同时⾃⼰也发送⼀个SYN包(syn=k),即SYN+ACK包,此
时服务器进⼊SYN_RECV状态;
3. 第三次握⼿:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进⼊
ESTABLISHED(TCP连接成功)状态,完成三次握⼿。
可以看到握⼿时会在客户端和服务器之间传递⼀些TCP头信息,⽐如ACK标志、SYN标志以及挥⼿时的FIN标志等。
除了以上这些常见的标志头信息,还有另外⼀些标志头信息,⽐如推标志PSH、复位标志RST等。其中复位标志RST的作⽤就是“复位相应的TCP连接”。
TCP连接和释放时还有许多细节,⽐如半连接状态、半关闭状态等。详情请参考这⽅⾯的巨著《TCP/IP详解》和《UNIX⽹络编程》。
另⼀个可能导致的“Connection reset”的原因是服务器设置了Socket.setLinger (true, 0)。但我检查过线上的tomcat配置,是没有使⽤该设置的,⽽且线上的服务器都使⽤了nginx进⾏反向代理,所以并不是该原因导致的。关于该原因上⾯的oracle⽂档也谈到了并给出了解释。
此外啰嗦⼀下,另外还有⼀种⽐较常见的错误“Connection reset by peer”,该错误和“Connection reset”是有区别的:服务器返回了“RST”时,如果此时客户端正在从Socket套接字的输出流中读数据则会提⽰Connection reset”;
服务器返回了“RST”时,如果此时客户端正在往Socket套接字的输⼊流中写数据则会提⽰“Connection reset by peer”。“Connection reset by peer”如下图所⽰:
前⾯谈到了导致“Connection reset”的原因,⽽具体的解决⽅案有如下⼏种:
出错了重试;
客户端和服务器统⼀使⽤TCP长连接;
客户端和服务器统⼀使⽤TCP短连接。
⾸先是出错了重试:这种⽅案可以简单防⽌“Connection reset”错误,然后如果服务不是“幂等”的则不能使⽤该⽅法;⽐如提交订单操作就不是幂等的,如果使⽤重试则可能造成重复提单。
然后是客户端和服务器统⼀使⽤TCP长连接:客户端使⽤TCP长连接很容易配置(直接设置HttpClient就好),⽽服务器配置长连接就⽐较⿇烦了,就拿tomcat来说,需要设置tomcat的maxKeepAliveRequests、connectionTimeout等参数。另外如果使⽤了nginx进⾏反向代理或负载均衡,
此时也需要配置nginx以⽀持长连接(nginx默认是对客户端使⽤长连接,对服务器使⽤短连接)。
使⽤长连接可以避免每次建⽴TCP连接的三次握⼿⽽节约⼀定的时间,但是我这边由于是内⽹,客户端和服务器的3次握⼿很快,⼤约只需1ms。ping⼀下⼤约0.93ms(⼀次往返);三次握⼿也是⼀次往返(第三次握⼿不⽤返回)。根据80/20原理,1ms可以忽略不计;⼜考虑到长连接的扩展性不如短连接好、修改nginx和tomcat的配置代价很⼤(所有后台服务都需要修改);所以这⾥并没有使⽤长连接。ping服务器的时间如下图:
最后的解决⽅案是客户端和服务器统⼀使⽤TCP短连接:我这边正是这么⼲的,⽽使⽤短连接既不⽤改nginx配置,也不⽤改tomcat配置,只需在使⽤HttpClient时使⽤http1.0协议并增加http请求的header信息(Connection: Close),源码如下:
httpGet.setProtocolVersion(HttpVersion.HTTP_1_0);
httpGet.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
最后再补充⼏句,虽然对于每次请求TCP长连接只能节约⼤约1ms的时间,但是具体是使⽤长连接还是短连接还是要衡量下,⽐如你的服务每天的pv是1亿,那么使⽤长连接节约的总时间为:
1亿*1ms=10^8*1ms=10^5*1s=27.78h
神奇的是,亿万级pv的服务使⽤长连接⼀天内节约的总时间为27.78⼩时(竟然⼤于⼀天),所以使⽤长连接还是短连接⼤家需要根据⾃⼰的服务访问量、扩展性等因素衡量下。但是⼀定要注意:服务器和客户端的连接⼀定要保持⼀致,要么都是长连接,要么都是短连接。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论