Docker Swarm - Overlay 網(wǎng)絡(luò)長(zhǎng)連接問題

問題描述

如圖所示,在 Swarm 集群中部署了 ServiceAServiceB 這兩個(gè)服務(wù),服務(wù)間通過(guò) grpc 建立長(zhǎng)連接實(shí)現(xiàn)服務(wù)間調(diào)用。然而 ServiceA 在調(diào)用 ServiceB 時(shí),偶爾會(huì)出現(xiàn)如下錯(cuò)誤:

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)

在我們查看容器日志時(shí),這個(gè)錯(cuò)誤出現(xiàn)次數(shù)不是很頻繁,但是一定會(huì)出現(xiàn),由于這個(gè)錯(cuò)誤會(huì)導(dǎo)致業(yè)務(wù)系統(tǒng)異常,所以我們花了點(diǎn)時(shí)間去處理它。

問題排查

1、 grpc 中間件的問題?
并發(fā)測(cè)試:50 個(gè)線程,10萬(wàn)次請(qǐng)求,重復(fù)了 3 次,均能正常響應(yīng)。因此,排除這種可能性。

2、測(cè)試環(huán)境網(wǎng)絡(luò)波動(dòng)導(dǎo)致的?
持續(xù)請(qǐng)求測(cè)試:多線程持續(xù)請(qǐng)求 4 小時(shí),均能正常響應(yīng)。然而另外一套測(cè)試環(huán)境,測(cè)試妹子人工測(cè)試的時(shí)候還是出現(xiàn)這個(gè)問題。因此,排除這種可能性。

3、搜索 Connection reset by peer 相關(guān)信息
網(wǎng)上很多文章都說(shuō)明了這個(gè)異??赡艹霈F(xiàn)的原因,經(jīng)過(guò)各種 DEBUG,發(fā)現(xiàn)這個(gè)異常發(fā)生時(shí),ServiceA 沒有將數(shù)據(jù)發(fā)送到 ServiceB。結(jié)合上述 1 和 2 兩步的測(cè)試,長(zhǎng)連接一直維持時(shí)無(wú)異常;人工測(cè)試時(shí),中途會(huì)停止請(qǐng)求,時(shí)間過(guò)長(zhǎng),長(zhǎng)連接會(huì)斷開,ServiceA 無(wú)法將數(shù)據(jù)發(fā)送給 ServiceB,就能解釋通了。

4、分析 Docker Swarm 中的網(wǎng)絡(luò)模型

Docker Swarm 中使用 IPVS 將 ServiceA 的請(qǐng)求路由到 ServiceB 的一個(gè)實(shí)例,ServiceAServiceB 長(zhǎng)連接的建立會(huì)經(jīng)過(guò) IPVS。此處 IPVS 的規(guī)則是:當(dāng) TCP 會(huì)話空閑超過(guò)15分鐘(900秒)時(shí),IPVS 連接超時(shí)并從連接表中清除,即圖中 IPVS 與 ServiceB 之間的連接。

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

默認(rèn) IPVS timeout 值:

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

默認(rèn) TCP timeout 值:

  • tcp_keepalive_time = **7200** (秒,連接時(shí)長(zhǎng))
  • tcp_keepalive_intvl = 75 (秒,探測(cè)時(shí)間間隔)
  • tcp_keepalive_probes = 9 (次,探測(cè)頻率)

當(dāng) IPVS 超時(shí), 它將從連接表中清除。而 IPVS 超時(shí)后,時(shí)間在 7200s 之內(nèi),ServiceA 還是會(huì)認(rèn)為長(zhǎng)連接處于連接狀態(tài),實(shí)則不然,繼續(xù)調(diào)用 ServiceB 則會(huì)出現(xiàn)問題。

5、精準(zhǔn)復(fù)現(xiàn)問題
ServiceA 調(diào)用 ServiceB,正常響應(yīng),等待 15 分鐘以上,ServiceA 繼續(xù)調(diào)用 ServiceB,一定出現(xiàn) Connection reset by peer 的異常。

問題解決

方式一:ServiceA 在代碼層面實(shí)現(xiàn)連接重試邏輯

方式二:系統(tǒng)層面設(shè)置 TCP 的 timeout

設(shè)置 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,添加如下內(nèi)容:

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

為了使配置生效,必須重啟 Swarm 中的服務(wù)。建議同時(shí)應(yīng)用上述的兩種方法。

參考文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容