Docker Swarm - Overlay 網絡長連接問題

問題描述

如圖所示,在 Swarm 集群中部署了 ServiceAServiceB 這兩個服務,服務間通過 grpc 建立長連接實現服務間調用。然而 ServiceA 在調用 ServiceB 時,偶爾會出現如下錯誤:

java.io.IOException: Connection reset by peer
    at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
    at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
    at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
    at sun.nio.ch.IOUtil.read(IOUtil.java:192)
    at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:380)
    at io.netty.buffer.PooledUnsafeDirectByteBuf.setBytes(PooledUnsafeDirectByteBuf.java:288)
    at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1100)
    at io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:367)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:118)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:642)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:565)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:479)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:441)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
    at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
    at java.lang.Thread.run(Thread.java:745)

在我們查看容器日志時,這個錯誤出現次數不是很頻繁,但是一定會出現,由于這個錯誤會導致業務系統異常,所以我們花了點時間去處理它。

問題排查

1、 grpc 中間件的問題?
并發測試:50 個線程,10萬次請求,重復了 3 次,均能正常響應。因此,排除這種可能性。

2、測試環境網絡波動導致的?
持續請求測試:多線程持續請求 4 小時,均能正常響應。然而另外一套測試環境,測試妹子人工測試的時候還是出現這個問題。因此,排除這種可能性。

3、搜索 Connection reset by peer 相關信息
網上很多文章都說明了這個異常可能出現的原因,經過各種 DEBUG,發現這個異常發生時,ServiceA 沒有將數據發送到 ServiceB。結合上述 1 和 2 兩步的測試,長連接一直維持時無異常;人工測試時,中途會停止請求,時間過長,長連接會斷開,ServiceA 無法將數據發送給 ServiceB,就能解釋通了。

4、分析 Docker Swarm 中的網絡模型

Docker Swarm 中使用 IPVS 將 ServiceA 的請求路由到 ServiceB 的一個實例,ServiceAServiceB 長連接的建立會經過 IPVS。此處 IPVS 的規則是:當 TCP 會話空閑超過15分鐘(900秒)時,IPVS 連接超時并從連接表中清除,即圖中 IPVS 與 ServiceB 之間的連接。

下面是兩種不同的 timeout ,一種是 IPVS 的,另一種是 TCP 的:

默認 IPVS timeout 值:

  • ipvsadm -l --timeout
  • Timeout (tcp tcpfin udp): **900** 120 300

默認 TCP timeout 值:

  • tcp_keepalive_time = **7200** (秒,連接時長)
  • tcp_keepalive_intvl = 75 (秒,探測時間間隔)
  • tcp_keepalive_probes = 9 (次,探測頻率)

當 IPVS 超時, 它將從連接表中清除。而 IPVS 超時后,時間在 7200s 之內,ServiceA 還是會認為長連接處于連接狀態,實則不然,繼續調用 ServiceB 則會出現問題。

5、精準復現問題
ServiceA 調用 ServiceB,正常響應,等待 15 分鐘以上,ServiceA 繼續調用 ServiceB,一定出現 Connection reset by peer 的異常。

問題解決

方式一:ServiceA 在代碼層面實現連接重試邏輯

方式二:系統層面設置 TCP 的 timeout

設置 tcp_keepalive_time 小于 900s ,建議 600 ~ 800

sysctl -w net.ipv4.tcp_keepalive_time=600
sysctl -w net.ipv4.tcp_keepalive_intvl=30
sysctl -w net.ipv4.tcp_keepalive_probes=10

或者編輯文件 /etc/sysctl.conf,添加如下內容:

net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 10

為了使配置生效,必須重啟 Swarm 中的服務。建議同時應用上述的兩種方法。

參考文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容