某個(gè)微服務(wù)上線后,經(jīng)常拋出unexpected end of stream on Connection異常。懷疑是服務(wù)端斷開(kāi)長(zhǎng)鏈接,而客戶端依舊使用該連接調(diào)用。
調(diào)用鏈路如圖所示:

1. 臨時(shí)方案
根據(jù)異常信息定位到:服務(wù)端長(zhǎng)鏈接失效。客戶端依舊使用失效的長(zhǎng)鏈接去訪問(wèn)。
當(dāng)前的解決方案是:增加重試機(jī)制:retryOnConnectionFailure(true),當(dāng)客戶端使用失效的長(zhǎng)鏈接訪問(wèn)服務(wù)器失敗時(shí),重新訪問(wèn)。
但是這個(gè)是臨時(shí)的解決方案,可以使得業(yè)務(wù)不受影響,但是依舊無(wú)法解決服務(wù)端長(zhǎng)鏈接失效的問(wèn)題。
2. 問(wèn)題分析
2.1 服務(wù)端長(zhǎng)鏈接失效時(shí)間
登錄服務(wù)器(因?yàn)槭俏⒎?wù)調(diào)用,服務(wù)器自己是可以登錄上的)。
cat /proc/sys/net/ipv4/tcp_keepalive_time
但得到的結(jié)果是默認(rèn)的7200秒。
2.2 wireshark抓包分析
登錄服務(wù)器,使用tcpdump命令抓包分析,工具的使用詳見(jiàn)——TCP抓包分析—以及wireshark工具下載使用

- 客戶端經(jīng)過(guò)3次握手過(guò)程和服務(wù)端建立長(zhǎng)鏈接;
- 在8s內(nèi)長(zhǎng)鏈接沒(méi)有請(qǐng)求,服務(wù)端發(fā)起了四次揮手(FIN);
- 客戶端收到FIN后,發(fā)送ACK確認(rèn)收到;
- 客戶端通知應(yīng)用程序是否給服務(wù)器發(fā)送FIN時(shí);okhttp3使用該連接進(jìn)行通信;
- 發(fā)生RST過(guò)程,于是拋出了上述的異常。
下面是一個(gè)鏈接正常的建立和銷(xiāo)毀:

可以看到,當(dāng)該鏈接8s內(nèi)無(wú)數(shù)據(jù)時(shí),服務(wù)端會(huì)主動(dòng)的斷開(kāi)連接。
由上述抓包分析可知:關(guān)鍵點(diǎn)8s內(nèi)無(wú)請(qǐng)求訪問(wèn),服務(wù)器斷開(kāi)長(zhǎng)鏈接;
3.2 代碼配置排查
項(xiàng)目使用的是SpringCloud全家桶進(jìn)行開(kāi)發(fā)。替換了tomcat服務(wù)器使用了undertow服務(wù)器。Spring Boot 內(nèi)嵌容器Undertow取代tomcat
查看配置信息時(shí),發(fā)現(xiàn):
org.springframework.boot.autoconfigure.web.ServerProperties.Undertow配置地址:

由此可知:
- 服務(wù)器是否開(kāi)啟長(zhǎng)鏈接由alwaysSetKeepAlive控制;
- 當(dāng)一個(gè)鏈接noRequestTimeout內(nèi)空閑,服務(wù)器便會(huì)關(guān)閉鏈接;
如代碼所示:

當(dāng)server的worker在noRequestTimeout空閑時(shí),便會(huì)shutdown該鏈接。于是改鏈接向客戶端發(fā)起FIN配置。
3.3 問(wèn)題解決
也就是設(shè)置noRequestTimeout便可以去解決:服務(wù)器關(guān)閉長(zhǎng)鏈接的問(wèn)題。但是配置文件中未設(shè)置該參數(shù),這個(gè)8s是在哪里來(lái)的?
經(jīng)過(guò)排查發(fā)現(xiàn),配置文件中存在下列配置:
server:
connection-timeout: 8000
源碼位置:org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer#customize

也就是設(shè)置了connection-timeout超時(shí)時(shí)間影響了服務(wù)端長(zhǎng)連接關(guān)閉的時(shí)間。
最終解決方案:
server:
connection-timeout: 60000 # 默認(rèn)60s,此時(shí)顯式的設(shè)置為60s。