【發(fā)現(xiàn)問題】
2019年3月20日某個項目組報告稱A機房的應用日志,每隔一段時間報出一次丟失Sentinel連接的異常。
[00:38:55.256] {Thread-11}Lost connection to Sentinel at xx.xx.xx.40:26379. Sleeping 5000ms and retrying.
[00:38:55.256] {Thread-12}Lost connection to Sentinel at xx.xx.xx.39:26379. Sleeping 5000ms and retrying.
[00:38:55.256] {Thread-10}Lost connection to Sentinel at xx.xx.xx.41:26379. Sleeping 5000ms and retrying.
。。。。。。
[02:50:32.344] {Thread-11}Lost connection to Sentinel at xx.xx.xx.40:26379. Sleeping 5000ms and retrying.
[02:50:32.344] {Thread-10}Lost connection to Sentinel at xx.xx.xx.41:26379. Sleeping 5000ms and retrying.
[02:50:32.345] {Thread-12}Lost connection to Sentinel at xx.xx.xx.39:26379. Sleeping 5000ms and retrying.
。。。。。。
[05:02:09.432] {Thread-12}Lost connection to Sentinel at xx.xx.xx.39:26379. Sleeping 5000ms and retrying.
[05:02:09.432] {Thread-11}Lost connection to Sentinel at xx.xx.xx.40:26379. Sleeping 5000ms and retrying.
[05:02:09.432] {Thread-10}Lost connection to Sentinel at xx.xx.xx.41:26379. Sleeping 5000ms and retrying.
。。。。。。
【分析問題】
Sentinel是redis的監(jiān)察節(jié)點,三個IP地址正是A機房redis的一套Sentinel地址。該項目組剛接入kafka并啟用了自研的托管offset到redis的功能,所以會出現(xiàn)跟redis的連接。
查詢該異??芍蛻舳烁鶶entinel的連接屬于長連接,結合兩次異常之間的間隔都是2小時11分27秒,聯(lián)想到linux的默認keepalive為7500即2小時,猜測長連接已被意外斷開,在每2小時的keepalive時檢查到連接不可達所以拋出異常。
登錄A機房Sentinel所在機器,查詢26379端口的連接情況如下,共有980個客戶端連接,且都處于ESTABLISHED狀態(tài),而且客戶端幾乎都落在xx.xx.xx.149,xx.xx.xx.150,xx.xx.xx.153上。
netstat -anp | grep 26379
tcp 0 0 xx.xx.xx.39:26379 xx.xx.xx.153:47914 ESTABLISHED 9752/./src/redis-se
tcp 0 0 xx.xx.xx.39:26379 xx.xx.xx.150:55064 ESTABLISHED 9752/./src/redis-se
tcp 0 0 xx.xx.xx.39:26379 xx.xx.xx.153:59528 ESTABLISHED 9752/./src/redis-se
tcp 0 0 xx.xx.xx.39:26379 xx.xx.xx.150:48246 ESTABLISHED 9752/./src/redis-se
tcp 0 0 xx.xx.xx.39:26379 xx.xx.xx.150:43686 ESTABLISHED 9752/./src/redis-se
tcp 0 0 xx.xx.xx.39:26379 xx.xx.xx.150:55588 ESTABLISHED 9752/./src/redis-se
tcp 0 0 xx.xx.xx.39:26379 xx.xx.xx.153:58350 ESTABLISHED 9752/./src/redis-se
tcp 0 0 xx.xx.xx.39:26379 xx.xx.xx.150:59076 ESTABLISHED 9752/./src/redis-se
。。。。。。
登錄客戶端所在機器xx.xx.xx.149,查詢26379端口的連接情況如下,只有3個Sentinel的一套連接。
netstat -anp | grep 26379
tcp 0 0 xx.xx.xx.149:53610 xx.xx.xx.39:26379 ESTABLISHED 28548/java
tcp 0 0 xx.xx.xx.149:42184 xx.xx.xx.40:26379 ESTABLISHED 28548/java
tcp 0 0 xx.xx.xx.149:53520 xx.xx.xx.41:26379 ESTABLISHED 28548/java
經(jīng)運維查詢證實客戶端機器xx.xx.xx.149的keepalive為7500, probes為9, intvl為75,即在 7500 + 75 * 9 = 2小時11分15秒時能探知連接不可達并多次嘗試后決定連接失效然后清理,這和日志表達出來的連接失效的間隔時間幾乎一致。
【初步結論】
所以可以斷定長連接被意外斷開后,客戶端已通過keepalive清理掉失效長連接,并重建新的一輪長連接。但服務端無感知,保持失效長連接處于ESTABLISHED狀態(tài)。
接下來要查找長連接被意外斷開的原因,登錄另一個機房B的Sentinel所在機器查詢26379端口的連接情況,總共只有130個客戶端連接,說明A機房的redis長連接斷開是個例。這里的reids處于xx.xx.19網(wǎng)段,客戶端處于xx.xx.32網(wǎng)段,所以我們猜測redis長連接被斷開同樣是因為A機房子網(wǎng)間的網(wǎng)關防火墻造成。
經(jīng)運維查詢證實,xx.xx.19網(wǎng)段和xx.xx.32網(wǎng)段之間的防火墻,設置有限定超時時間1小時,任何連接在1小時內(nèi)沒有任意的數(shù)據(jù)報文通過,即會被防火墻斷開??蛻舳嗽摌I(yè)務使用頻次較低,很可能幾個小時沒有請求,而客戶端的keepalive使用的是默認值2小時,超過了防火墻設置的1小時。
所以每1小時防火墻發(fā)現(xiàn)沒有報文通過就斷開連接,客戶端和服務端此時都無感知。每2小時客戶端的keepalive發(fā)送心跳包時,探知到連接不可用所以就拋出異常。
【思考】
1)為什么能夠確定客戶端發(fā)現(xiàn)連接不可用拋出異常后,重建了新的一輪連接?
通過源碼得知,catch異常打印丟失連接的日志后,方法并沒有退出,而是在while循環(huán)里重頭再來,新建Jedis發(fā)起長連接訂閱“switch-master”事件。
從現(xiàn)象上來看,客戶端間隔性的打印丟失連接的日志后,登錄客戶端所在機器xx.xx.xx.149,查詢26379端口,依然能看到一整套Sentinel的連接,也能輕易的判斷出這一點。

2)為什么服務端redis沒有清理無效的長連接?
# A reasonable value for this option is 60 seconds.
tcp-keepalive 0
因為redis默認沒有開啟keepalive,所以防火墻斷開連接后,客戶端通過keepalive檢測清理失效連接并進行重建,服務端沒有做出任何措施所以會造成連接堆積。
【解決問題】
有三種思路可以解決這個問題:
1)調(diào)整防火墻策略,上調(diào)或者放過部分端口的超時設置。但這會增加運維對全機房的維護負擔。
2)調(diào)整客戶端的keepalive間隔,但這需要每一臺機器都要配一次,如果新機器有遺漏,問題還會重現(xiàn)。
3)調(diào)整服務端,打開keepalive并設置一個合理值,這樣可以不用在意客戶端的keepalive配置。因為對防火墻來說,業(yè)務數(shù)據(jù)包,客戶端發(fā)起的來回的keepalive包,服務端發(fā)起的來回的keepalive包,都是有效的。
最終選擇的是3)設置redis的tcp-keepalive=600(10分鐘)。