www.icesr.com
IT运维工程师的摇篮

Tomcat日志报错Connection reset原因的分析与解决方案

最近在线上环境遇见这样一个问题,Tomcat的日志中每天会出现部分Connection reset错误信息,通过分析和网上查询之后得出:

产生的原因:
导致“Connection reset”的原因是服务器端因为某种原因关闭了Connection,而客户端任然在读写数据,此时服务器就会返回复位标志“RST”,然后此时客户端就会提示:“java.net.SocketException:Connection rest”。

关于复位标志“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连接”。

前面说到出现“Connection reset”的原因是服务器关闭了Connection[调用了Socket.close()方法]。大家可能有疑问了:服务器关闭了Connection为什么会返回“RST”而不是返回“FIN”标志。原因在于Socket.close()方法的语义和TCP的“FIN”标志语义不一样:发送TCP的“FIN”标志表示我不再发送数据了,而Socket.close()表示我不在发送也不接受数据了。问题就出在“我不接受数据” 上,如果此时客户端还往服务器发送数据,服务器内核接收到数据,但是发现此时Socket已经close了,则会返回“RST”标志给客户端。当然,此时客户端就会提示:“Connection reset”。

在此另强调一下,还有一种比较常见的错误“Connection reset by peer”,该错误和“Connection reset”是有区别的:
– 服务器返回了“RST”时,如果此时客户端正在从Socket套接字的输出流中读数据则会提示Connection reset”;
– 服务器返回了“RST”时,如果此时客户端正在往Socket套接字的输入流中写数据则会提示“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.97ms(一次往返);三次握手也是一次往返(第三次握手不用返回)。根据80/20原理,1ms可以忽略不计;又考虑到长连接的扩展性不如短连接好、修改nginx和tomcat的配置代价很大(所有后台服务都需要修改);所以这里并没有使用长连接。

解决办法:

这里我们线上的nginx服务器和tomcat之间的连接使用的就是长连接,但是由于当时考虑不周忽略掉了nginx上游节点tomcat的参数配置而导致了 “Connection reset”问题。
现对nginx和tomcat进行参数配置调整如下:
nginx配置:

upstream  exchange  {
                        server   10.47.18.242:18020;
                        server   10.25.69.215:18020;
                        keepalive 10;
                       #连接到上游服务器的最大并发空闲keepalive长连接数(默认是未设置,建议与Tomcat Connector中的maxKeepAliveRequests值一样)
                       #当这个数被超过时,使用"最近最少使用算法(LUR)"来淘汰并关闭连接。
                       }

tomcat配置:

                    #建议和nginx中的keepalive 配置次数保持一致
                    maxKeepAliveRequests="10"    
                    connectionTimeout="20000"

通过一周的观察,再也没有出现“Connection reset”错误。

问题总结:
虽然对于每次请求TCP长连接只能节约大约1ms的时间,但是具体是使用长连接还是短连接还是要根据你的线上环境来衡量,比如你的服务每天的pv是1000w,那么使用长连接(keepalive 10;)节约的总时间为:
(10-1)*1.0*10^6 * 1ms = 9 * 10^6 ms = 9 * 10^3h/3600≈2.5h

所以使用长连接还是短连接大家需要根据自己的服务访问量、扩展性等因素衡量下。但是一定要注意:服务器和客户端的连接一定要保持一致,要么都是长连接,要么都是短连接。

未经允许不得转载:冰点网络 » Tomcat日志报错Connection reset原因的分析与解决方案

分享到:更多 ()

评论 抢沙发

评论前必须登录!