Redis線(xiàn)上問(wèn)題后續(xù)真正的原因

轉(zhuǎn)載:https://www.cnblogs.com/devilwind/p/6865750.html
一、問(wèn)題現(xiàn)象

  • redis服務(wù)端的sentinel模塊存在大量的established狀態(tài)的連接,并且這些連接一直不被釋放,而客戶(hù)端的連接數(shù)正常。

二、問(wèn)題排查過(guò)程

1、根據(jù)連接狀態(tài)進(jìn)行推斷

  • 服務(wù)端存在大量的連接的狀態(tài)為established,而客戶(hù)端連接數(shù)正常,這說(shuō)明客戶(hù)端連接是被非正常的方式關(guān)閉的,因?yàn)楦鶕?jù)TCP關(guān)閉連接的四次握手協(xié)議來(lái)看,只要客戶(hù)端正常發(fā)起了關(guān)閉動(dòng)作,服務(wù)端的狀態(tài)只可能為CLOSE_WAIT/LAST_ACK/CLOSED中的一種。
  • 非正常關(guān)閉連接的情況有斷電、斷網(wǎng)后服務(wù)器重啟等等,但這些場(chǎng)景出現(xiàn)的次數(shù)較少,不會(huì)造成這么多連接,究竟是怎樣被正常關(guān)閉,到這一步還無(wú)法判斷,只能先從其它方面入手。

2、檢查業(yè)務(wù)系統(tǒng)的redis配置和程序邏輯

  • 檢查redis配置,看是否有不規(guī)范的配置和不規(guī)范的使用redis的代碼,經(jīng)過(guò)排查,確定openapi有直接使用JedisSentinelPool類(lèi),但并未在spring中配置destroy-method=”destroy”,即在停止應(yīng)用時(shí)釋放連接。
  • 排查其它系統(tǒng),未發(fā)現(xiàn)有不正常使用redis的情況,JedisSentinelPool造成的連接數(shù)增長(zhǎng)也是有限的,即只在啟動(dòng)時(shí)才創(chuàng)建sentinel連接,此處暫時(shí)排查是業(yè)務(wù)代碼使用redis不規(guī)范的問(wèn)題。

3、從網(wǎng)絡(luò)尋找線(xiàn)索

  • 通過(guò)百度搜索,找不到關(guān)于sentinel的該問(wèn)題的文章。
  • 查看redis、jedis、spring-data-redis官方網(wǎng)站的issues及releaseNotes,均未發(fā)現(xiàn)有提及該問(wèn)題。

4、重新思考產(chǎn)生連接的根源

  • 通過(guò)閱讀jedis源碼,發(fā)現(xiàn)連接sentinel的代碼只有1處,即系統(tǒng)啟動(dòng)時(shí)會(huì)先建立一個(gè)短連接查詢(xún)sentinel的主節(jié)點(diǎn),然后開(kāi)啟3(業(yè)務(wù)系統(tǒng)配置了3個(gè)地址)個(gè)線(xiàn)程創(chuàng)建3個(gè)長(zhǎng)連接與3個(gè)sentinel相連。短連接會(huì)立即釋放,而長(zhǎng)連接的創(chuàng)建放在循環(huán)中,問(wèn)題可能出現(xiàn)在該處,以下是源碼:

    public void run() {
          running.set(true);
          while (running.get()) {
            j = new Jedis(host, port);
            try {
              j.subscribe(new JedisPubSub() {
                @Override
                public void onMessage(String channel, String message) {
                  log.fine("Sentinel " + host + ":" + port + " published: " + message + ".");
                  String[] switchMasterMsg = message.split(" ");
                  if (switchMasterMsg.length > 3) {
                    if (masterName.equals(switchMasterMsg[0])) {
                      initPool(toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4])));
                    } else {
                      log.fine("Ignoring message on +switch-master for master name "
                         + switchMasterMsg[0] + ", our master name is " + masterName);
                    }
                  } else {
                    log.severe("Invalid message received on Sentinel " + host + ":" + port
                        + " on channel +switch-master: " + message);
                  }
                }
              }, "+switch-master");
            } catch (JedisConnectionException e) {
              if (running.get()) {
                log.severe("Lost connection to Sentinel at " + host + ":" + port
                    + ". Sleeping 5000ms and retrying.");
                try {
                  Thread.sleep(subscribeRetryWaitTimeMillis);
                } catch (InterruptedException e1) {
                  e1.printStackTrace();
                }
              } else {
                log.fine("Unsubscribing from Sentinel at " + host + ":" + port);
              }
            }
          }
        }
    

正常情況下 j.subscribe會(huì)產(chǎn)生阻塞,而出現(xiàn)異常時(shí)會(huì)重新創(chuàng)建連接并且打印日志“Lost connection to Sentinel at……”

  • 在業(yè)務(wù)系統(tǒng)搜索“Lost connection to Sentinel at”日志,把所有業(yè)務(wù)日志都搜索一遍,沒(méi)有找到該日志,最后終于在nohub日志中找到了(nohub日志在weblogic啟動(dòng)時(shí)會(huì)被清理,也導(dǎo)致找了幾次才找到該日志),通過(guò)UltraEdit提取所有該內(nèi)容的日志,發(fā)現(xiàn)了28條日志,將這些日志導(dǎo)出到excel比較,發(fā)現(xiàn)日志出現(xiàn)的時(shí)間間隔均為2小時(shí)11分15秒到2小時(shí)11分20秒左右,該日志出現(xiàn)的時(shí)間間隔非常有規(guī)律,這種規(guī)律一定與該問(wèn)題存在某種聯(lián)系。

一般在系統(tǒng)中設(shè)置時(shí)間間隔都會(huì)是個(gè)整數(shù),2小時(shí)11分15秒顯得有點(diǎn)怪異,然后將時(shí)間換算成秒,看是否為一個(gè)常用的或有規(guī)律的整數(shù),2小時(shí)=7200秒,突然想到操作系統(tǒng)的keepalive設(shè)置中有一個(gè)7200秒,查一下操作系統(tǒng)默認(rèn)的設(shè)置,參數(shù)如下:

net.ipv4.tcp_keepalive_time = 7200 默認(rèn) tcp空閑時(shí)間

net.ipv4.tcp_keepalive_intvl =75 默認(rèn)心跳檢測(cè)時(shí)間間隔

net.ipv4.tcp_keepalive_probes = 9 默認(rèn)檢測(cè)次數(shù)

如果完成9次心跳,仍然發(fā)現(xiàn)連接無(wú)效的時(shí)間為:7200+9*75=2小時(shí)11分15秒,由此,基本上可以判斷,客戶(hù)端的連接是被操作系統(tǒng)回收的,結(jié)合前面的分析,連接被回收時(shí)并未向服務(wù)端發(fā)送關(guān)閉的報(bào)文。

  • 通過(guò)在網(wǎng)絡(luò)上搜索,長(zhǎng)連接被阻斷的原因,看到了防火墻的字樣,進(jìn)一步了解,發(fā)現(xiàn)防火墻基本上都有該項(xiàng)功能,即給長(zhǎng)連接設(shè)置默認(rèn)的超時(shí)時(shí)間。

5、與運(yùn)維進(jìn)行溝通防火墻問(wèn)題

  • 剛開(kāi)始問(wèn)運(yùn)行同事A時(shí),同事A說(shuō)防火墻用的是juniper,但絕對(duì)沒(méi)有為長(zhǎng)連接設(shè)置默認(rèn)的超時(shí)時(shí)間。
  • 進(jìn)一步搜索juniper的資料,確定是有默認(rèn)30分鐘的超時(shí)時(shí)間。
  • 與運(yùn)維同事B溝通,確認(rèn)有默認(rèn)的超時(shí)時(shí)間。防火墻的策略是,當(dāng)建立連接時(shí),會(huì)在防火墻保存一條會(huì)話(huà)信息,記錄了源IP、源端口、目的IP、目的端口。當(dāng)在超時(shí)時(shí)間內(nèi),該會(huì)話(huà)代表的連接未有任何數(shù)據(jù)包時(shí),就會(huì)清除該會(huì)話(huà),此后若再有數(shù)據(jù)包過(guò)來(lái),由于沒(méi)有了會(huì)話(huà),防火墻會(huì)直接丟棄該會(huì)話(huà)。
  • 要同事B將sentinel的端口26379配置一條永不超時(shí)的策略。

6、持續(xù)觀察測(cè)試環(huán)境的日志

  • 發(fā)現(xiàn)在更改了防火墻超時(shí)策略后,仍然出現(xiàn)了一條“Lost connection to Sentinel at”日志,此后一直未出現(xiàn),由此可見(jiàn),防火墻確實(shí)對(duì)連接造成了影響。

7、初步結(jié)論

  • 防火墻每30分鐘將該連接的會(huì)話(huà)信息清除,從而導(dǎo)致客戶(hù)端操作系統(tǒng)檢測(cè)到心跳失敗,隨后操作系統(tǒng)清除了客戶(hù)端連接,使得客戶(hù)端的連接數(shù)能正常釋放,由于報(bào)文被防火墻丟棄,close信息也不會(huì)到達(dá)服務(wù)端,導(dǎo)致服務(wù)端的連接未被及時(shí)關(guān)閉。

8、繼續(xù)思考服務(wù)斷連接不被釋放的問(wèn)題

  • 按道理連接被防火墻阻斷后,既然客戶(hù)端的操作系統(tǒng)能正?;厥者B接,服務(wù)端應(yīng)該也可以,但服務(wù)端的keepalive機(jī)制為什么沒(méi)有發(fā)生作用?通過(guò)進(jìn)一步閱讀redis客戶(hù)端-jedis的源碼,發(fā)現(xiàn)jedis默認(rèn)開(kāi)啟了keepalive,繼續(xù)閱讀redis的源碼,發(fā)現(xiàn)redis默認(rèn)關(guān)閉了keepalive,到此所有疑問(wèn)已經(jīng)解決。

以上因素使得防火墻每30分鐘將該連接的會(huì)話(huà)信息清除,從而導(dǎo)致客戶(hù)端操作系統(tǒng)檢測(cè)到心跳失敗,隨后操作系統(tǒng)清除了客戶(hù)端連接,使得客戶(hù)端的連接數(shù)能正常釋放,隨后客戶(hù)端的jedis收到異常后重新創(chuàng)建連接,而服務(wù)端的keepalive并未執(zhí)行,以上過(guò)程不斷循環(huán),導(dǎo)致服務(wù)端established狀態(tài)的連接不斷增加并得不到釋放。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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