某个微服务上线后,经常抛出unexpected end of stream on Connection异常。怀疑是服务端断开长链接,而客户端依旧使用该连接调用。
调用链路如图所示:
image.png1. 临时方案
根据异常信息定位到:服务端长链接失效。客户端依旧使用失效的长链接去访问。
当前的解决方案是:增加重试机制:retryOnConnectionFailure(true)
,当客户端使用失效的长链接访问服务器失败时,重新访问。
但是这个是临时的解决方案,可以使得业务不受影响,但是依旧无法解决服务端长链接失效的问题。
2. 问题分析
2.1 服务端长链接失效时间
登录服务器(因为是微服务调用,服务器自己是可以登录上的)。
cat /proc/sys/net/ipv4/tcp_keepalive_time
但得到的结果是默认的7200秒。
2.2 wireshark抓包分析
image.png登录服务器,使用tcpdump命令抓包分析,工具的使用详见——TCP抓包分析—以及wireshark工具下载使用
- 客户端经过3次握手过程和服务端建立长链接;
- 在8s内长链接没有请求,服务端发起了四次挥手(FIN);
- 客户端收到FIN后,发送ACK确认收到;
- 客户端通知应用程序是否给服务器发送FIN时;okhttp3使用该连接进行通信;
- 发生RST过程,于是抛出了上述的异常。
image.png下面是一个链接正常的建立和销毁:
可以看到,当该链接8s内无数据时,服务端会主动的断开连接。
由上述抓包分析可知:关键点8s内无请求访问,服务器断开长链接;
3.2 代码配置排查
项目使用的是SpringCloud全家桶进行开发。替换了tomcat服务器使用了undertow服务器。Spring Boot 内嵌容器Undertow取代tomcat
查看配置信息时,发现:
org.springframework.boot.autoconfigure.web.ServerProperties.Undertow
配置地址:
由此可知:
- 服务器是否开启长链接由alwaysSetKeepAlive控制;
- 当一个链接noRequestTimeout内空闲,服务器便会关闭链接;
如代码所示:
image.png当server的worker在noRequestTimeout空闲时,便会shutdown该链接。于是改链接向客户端发起FIN配置。
3.3 问题解决
也就是设置noRequestTimeout便可以去解决:服务器关闭长链接的问题。但是配置文件中未设置该参数,这个8s是在哪里来的?
经过排查发现,配置文件中存在下列配置:
server:
connection-timeout: 8000
源码位置:org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer#customize
也就是设置了connection-timeout
超时时间影响了服务端长连接关闭的时间。
最终解决方案:
server:
connection-timeout: 60000 # 默认60s,此时显式的设置为60s。
网友评论