歡迎光臨
每天分享高質量文章

阿裡雲MySQL及Redis靈異斷連現象:安全組靜默丟包解決方法

點擊▲關註 “資料和雲”   給公眾號標星置頂

更多精彩 第一時間直達

導讀:雲計算時代的服務端網絡環境越來越複雜。不但要考慮實際的物理網絡,也要考慮到SDN/安全等技術的影響。理論上說,網絡對應用開發無感知,然而有時候也並非如此。本文作者記錄了一種阿裡雲上Redis/MySQL的靈異現象,並且記錄了問題原因,給出瞭解決方案。

引子:Redis client library 連接 Redis server 超時

差不多一兩年前,在阿裡雲上遇到一個奇怪的 Redis 連接問題,每隔十來分鐘,服務里的 Redis client 庫就報告連接 Redis server 超時,當時花了很大功夫,發現是阿裡雲會斷開長時間閑置的 TCP 連接,不給兩頭髮 FIN or RST 包,而當時我們的 Redis server 沒有打開 tcp_keepalive 選項,於是 Redis server 側那個連接還存在於 Linux conntrack table 里,而 Redis client 側由於連接池重用連接進行 get、set 發現連接壞掉就關閉了,所以 client 側的對應 local port 回收了,當接下來 Redis 重用這個 local port 向 Redis server 發起連接時,由於 Redis server 側的 conntrack table 里 四元組對應狀態是 ESTABLISHED,所以自然客戶端發來的 TCP SYN packet 被丟棄,Redis client 看到的現象就是連接超時。

解決這個問題很簡單,打開 Redis server 的 tcp_keepalive 選項就行。 然而當時沒想到,這個問題深層次的原因影響很重大,後果很嚴重!

孽債:”SELECT 1″ 觸發的 jdbc4.CommunicationsException

最近生產環境的 Java 服務幾乎每分鐘都報告類似下麵這種錯誤:

由於有之前調查 Redis 連接被阿裡雲異常中斷的先例,所以懷疑是類似問題,花了大量時間比對客戶端和服務端的 conntrack table,然而並沒有引子中描述的問題,然後又去比對多個 MySQL 服務器的 sysctl 設置,研究 iptables TRACE,研究 tcpdump 抓到的報文,試驗 tw_reuse, tw_recyle 等引數,調整 Aliyun 負載均衡器後面掛載的 MySQL 服務器個數,都沒效果, 反而意外發現一個**新問題**,在用如下命令不經過阿裡雲 SLB 直接連接資料庫時,有的資料庫可以在 600s 時傳回,有的則客戶端一直掛著,半個多小時了都退不出來,按 ctrl-c 中斷都不行。

當時檢查了一個正常的資料庫和一個不正常的資料庫,發現兩者的 wait_timeout 和 interactive_timeout 都是 600s,思索良苦,沒明白怎麼回事,然後偶然發現另外一個資料庫的 wait_timeout=60s,卻一下子明白了原始的 “select 1” 問題怎麼回事。

我們的服務使用了 Hikari JDBC 連接池[1],它的 idleTimeout 預設是 600s, maxLifetime 預設是 1800s,前者表示 idle JDBC connection 數量超過 minimumIdle 數目並且閑置時間超過 idleTimeout 則關閉此 idle connection,後者表示連接池裡的 connection 其生存時間不能超過 maxLifetime,到點了會被關掉。

在發現 “select 1” 問題後,我們以為是這倆引數比資料庫的 wait_timeout=600s 大的緣故,所以把這兩個引數縮小了,idleTimeout=570s, maxLifetime=585s,並且設置了 minimumIdle=5。但這兩個時間設置依然大於其中一個資料庫失誤設置的 wait_timeout=60s,所以閑置連接在 60s 後被 MySQL server 主動關閉,而 JDBC 並沒有什麼事件觸發回呼機制去關閉 JDBC connection,時間上也不夠 Hikari 觸發 idleTimeout 和 maxLifetime 清理邏輯,所以 Hikari 拿著這個“已經關閉”的連接,發了 “select 1” SQL 給服務器檢查連接有效性,於是觸發了上面的異常。

解決辦法很簡單,把那個錯誤配置的資料庫里 wait_timeout 從 60s 修正成 600s 就行了。 下麵繼續講述 “SELECT sleep(1000)” 會掛住退不出來的問題。

緣起:阿裡雲安全組與 TCP KeepAlive

最近看了一點佛教常識,對”諸法由因緣而起“的緣起論很是感慨,在調查 “SELECT sleep(1000)” 問題中,真實感受到了“由因緣而起” 的意思 

首先解釋下,為什麼有的資料庫服務器對 “SELECT sleep(1000)” 可以傳回,有的卻掛著退不出來。 其實 wait_timeout 和 interactive_timeout 兩個引數只對 “閑置” 的資料庫連接,也即沒有 SQL 正在執行的連接生效,對於 “SELECT sleep(1000)”,這是有一個正在執行的 SQL,其最大執行時間受 MySQL Server 的 max_execution_time 限制,這個引數在我司一般設置為 600s,這就是 “正常的資料庫” 在 600s 時 “SELECT sleep(1000)” 中斷執行而退出了。

但不走運的是(可以說又是個失誤配置 ),我們有的資料庫 max_execution_time 是 6000s,所以 “SELECT sleep(1000)” 在 MySQL server 服務端會在 1000s 時正常執行結束——但問題是,通過二分查找以及 tcpdump、iptables TRACE,發現阿裡雲會”靜默“丟棄 >=910s idle TCP connection,不給客戶端、服務端發送 FIN or RST 以強行斷掉連接,於是 MySQL server 在 1000s 結束時發給客戶端的 ACK+PSH TCP packet 到達不了客戶端,然後再過 wait_timeout=600s,MySQL server 就斷開了這個閑置連接——可憐的是,mysql client 這個命令列程式還一無所知,它很執著的等待 MySQL server 傳回,Linux 內核的 conntrack table 顯示這個連接一直是 ESTABLISHED,哪怕 MySQL server 端已經關閉對應的連接了,只是這個關閉動作的 FIN TCP packet 到不了客戶端!

下麵是 iptables TRACE 日誌對這個問題的實錘證明。

mysql 命令列所在機器的 iptables TRACE 日誌表明,mysql client 在 23:58:25 連接上了 mysql server,開始執行 SELECT sleep(1000),然後一直收不到服務器訊息,最後在 00:41:20 的時候我手動 kill 了 mysql 客戶端命令列行程,mysql 客戶端給 mysql server 發 FIN 包但收不到響應(此時 mysql server 端早關閉連接了)。

MySQL server 在 00:15:05 時執行 SELECT sleep(1000) 結束,給 mysql 客戶端回送結果,但 mysql 客戶端無響應(被阿裡雲丟包了,mysql 客戶端壓根收不到),在 00:25:05 時,由於 wait_timeout=600s,所以 MySQL server 給 mysql 客戶端發 FIN 包以斷開連接,自然,mysql 客戶端收不到,所以也沒有回應,結局是 MySQL server 一側的 Linux 內核反正自行關閉 TCP 連接了,mysql client 一側的 Linux 內核還在傻乎乎的在 conntrack table 維持著 ESTABLISHED 狀態的 TCP 連接,mysql client 命令列還在傻乎乎的 recv() 等著服務端傳回或者關閉鏈接。

Ok,現在知道是阿裡雲對 >= 910s 沒有發生 TCP packet 傳輸的虛擬機之間直連閑置 TCP 連接會“靜默”丟包,那麼是任意虛擬機之間嗎?是任意端口嗎?要求服務器掛到負載均衡器後面嗎?要求對應端口的併發連接到一定數目嗎?

在阿裡雲提交工單詢問後,沒得到什麼有價值信息,在經過艱苦卓絕的試驗後——每一次試驗要等近二十分鐘啊——終於功夫不負有心人,我發現一個穩定復現問題的規律了:

  1. 兩台虛擬機分別處於不同安全組,沒有共同安全組;

  2. 服務端的安全組開放端口 P 允許客戶端的安全組連接,客戶端不開放端口給服務端(按照一般有狀態防火牆的配置規則,都是只開服務端端口,不用開客戶端端口);

  3. 客戶端和服務端連接上後,閑置 >= 910s,不傳輸任何資料,也不傳輸有 keep alive 用途的 ack 包;

  4. 然後服務端在此長連接上發給客戶端的 TCP 包會在網絡上丟棄,到不了客戶端;

  5. 但如果客戶端此時給服務端發點資料,那麼會重新“激活”這條長鏈接,但此時還是單工狀態,客戶端能給服務端發包,服務端的包還到不了客戶端(大概是在服務端 OS 內核里重試中);

  6. 激活後,服務端再給客戶端發資料時,之前發送不出去的資料(如果還在內核里的 TCP/IP 協議棧重試中),加上新發的資料,會一起到達客戶端,此後這條長連接進入正常的雙工工作狀態;

下圖是用 nc 試驗的結果。

 

 

 

服務端

 

 

客戶端

 

在跟網友討論後,認識到這應該是阿裡雲安全組基於“集中式防火牆”實現導致的,因為集中式防火牆處於網絡中心樞紐,它要應付海量連接,因此其記憶體里的 conntrack table 需要比較短的 idle timeout(目前是 910s),以把長時間沒活躍的 conntrack record 清理掉節約記憶體,所以上面問題的根源就清晰了:

  1. client 連接 server,安全組(其實是防火牆)發現規則允許,於是加入一個記錄到 conntrack table;

  2. client 和 server 到了 910s 還沒資料往來,所以安全組把 conntrack 里那條記錄去掉了;

  3. server 在 910s 之後給 client 發資料,資料包到了安全組那裡,它一看 conntrack table 里沒記錄,而 client 側安全組又不允許這個端口的包通過,所以丟包了,於是 server -> client 不通;

  4. client 在同一個長連接上給 server 發點資料,安全組一看規則允許,於是加入 conntrack table 里;

  5. server 重試的資料包,或者新資料包,通過安全組時,由於已經有 conntrack record 了,所以放行,於是能到達客戶端了。

原因知道了,怎麼繞過這個問題呢?阿裡雲給了我兩個無法接受的 workaround:

  1. 把 server、client 放進同一個安全組;

  2. 修改 client 所在安全組,開放所有端口給 server 所在安全組;

再琢磨下,通過 netstat -o 發現我們的 Java 服務使用的 Jedis 庫和 mysql JDBC 庫都對 socket 檔案句柄打開了 SO_KEEPALIVE 選項[2]:

 

 

而 MySQL server 也對其打開的 socket 檔案句柄打開了 SO_KEEPALIVE 選項,所以我只用修改下服務端和客戶端至少其中一側的對應 sysctl 選項即可,下麵是我司服務端的預設配置,表示 TCP 連接閑置 1800s 後,每隔 30s 給對方發一個 ACK 包,最多發 3 次,如果在此期間對方回覆了,則計時器重置,再等 1800s 閑置條件,如果發了 3 次後對方沒反應,那麼會給對端發 RST 包同時關閉本地的 socket 檔案句柄,也即關閉這條長連接。

由於阿裡雲跨安全組的 910s idle timeout 限制,所以需要把 net.ipv4.tcp_keepalive_time 設置成小於 910s,比如 300s。

預設的 tcp_keepalive_time 特別大,這也解釋了為什麼當初 Redis client 設置了 SO_KEEPALIVE 選項後還是被阿裡雲靜默斷開。

如果某些網絡庫封裝之後沒有提供 setsockopt() 呼叫的機會,那麼需要用 LD_PRELOAD 之類的黑科技強行設置了,只有打開了 socket 檔案句柄的 SO_KEEPALIVE 選項,上面三個 sysctl 才對這個 socket 檔案句柄生效,當然,代碼里可以用 setsockopt() 函式進一步設置 keep_alive_intvl 和 keepalive_probes,不用 Linux 內核的全域性預設設置。

最後,除了 Java 家對 SO_KEEPALIVE 處理的很好,利用 netstat -o 檢查得知,對門的 NodeJS 家,其著名 Redis client library 開了 SO_KEEPALIVE 但其著名 mysql client library 並沒有開,而 Golang 家則嚴謹多了,兩個庫都開了 SO_KEEPALIVE。 為什麼引子里說這個問題很嚴重呢?因為但凡服務端處理的慢點,比如 OLAP 場景,不經過阿裡雲 SLB 直連服務端在 910s 之內沒傳回資料的話,就有可能沒機會傳回資料給客戶端了啊,這個問題查死人有沒有! 你可能問我為啥不通過阿裡雲 SLB 中轉,SLB 不會靜默丟包啊——但它的 idle timeout 上限是 900s 啊!!!

頭圖 Photo credit: “No Bugs” Hare

文中鏈接:

[1] https://github.com/brettwooldridge/HikariCP

[2] http://www.tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html

轉載自:高可用架構

資源下載

關註公眾號:資料和雲(OraNews)回覆關鍵字獲取

2018DTCC , 資料庫大會PPT

2018DTC,2018 DTC 大會 PPT

DBALIFE ,“DBA 的一天”海報

DBA04 ,DBA 手記4 電子書

122ARCH ,Oracle 12.2體系結構圖

2018OOW ,Oracle OpenWorld 資料

    赞(0)

    分享創造快樂