解決springboot中鏈接redis會(huì)timeout的問題
????之前寫項(xiàng)目遇見一個(gè)問題,就是我的項(xiàng)目上線后,總是經(jīng)常會(huì)報(bào)錯(cuò)timeout,我一看是redis鏈接timeout,首先我端口打開了,為啥還報(bào)錯(cuò)我就很納悶,只得先重啟一下防火墻。重啟過后的確可以正常訪問了,但是一段時(shí)間后依然還是會(huì)出現(xiàn)timeout的錯(cuò)誤。只能在重啟,這個(gè)問題始終沒有得到根本性的解決!
? ??
????因?yàn)槲沂褂玫氖莝pringboot,所以一開始并沒有往代碼方向去考慮,直接懷疑服務(wù)器有問題,(比較springboot目前用的人比較多也沒出啥問題也就比較相信它,同時(shí)我服務(wù)器又是最便宜的哪一款,就懷疑到服務(wù)器頭上了,便宜沒好貨哈哈),但還是沒有發(fā)現(xiàn)有啥問題,最后只能去看代碼...
????后面我用jedis重新寫了個(gè)小test,發(fā)現(xiàn)正常鏈接,但是springboot的redisTemplate就是有問題,那原因清楚了,可能真的是springboot的問題~
????我看了springboot內(nèi)嵌實(shí)現(xiàn)鏈接redis的源碼,發(fā)現(xiàn)springboot內(nèi)嵌了Luttcure的技術(shù),Luttuce和jedis pool都是一種池化技術(shù),就和數(shù)據(jù)庫的Durid、HkariaCP等這些鏈接池一樣。
而Luttuce有個(gè)問題就是Luttuce不會(huì)自動(dòng)刷新redis的拓?fù)浣Y(jié)構(gòu)的,所以會(huì)造成一段時(shí)間過后導(dǎo)致鏈接超時(shí),畢竟鏈接池也是有最大等待時(shí)間的。
拓?fù)浣Y(jié)構(gòu)
拓?fù)浣Y(jié)構(gòu)也稱為樹狀主從架構(gòu),咱們平常熟悉的主從架構(gòu)便是通過讀寫分離來降低住服務(wù)器的壓力,但是一般master節(jié)點(diǎn)進(jìn)行寫操作,slave節(jié)點(diǎn)進(jìn)行讀操作,在讀多寫少的場景,雖然降低了主服務(wù)器的壓力,比如執(zhí)行keys sort等操作保證了master的穩(wěn)定性,但如果寫并發(fā)某個(gè)時(shí)刻比較高時(shí),主節(jié)點(diǎn)需要向掛載的多個(gè)slave節(jié)點(diǎn)發(fā)送寫命令,這就又可能導(dǎo)致master的負(fù)載過高而影響服務(wù)的穩(wěn)定性。
因此我們就產(chǎn)生了樹狀主從架構(gòu),slave節(jié)點(diǎn)可以復(fù)制主節(jié)點(diǎn)數(shù)據(jù),也可以作為其他slave節(jié)點(diǎn)的master節(jié)點(diǎn)繼續(xù)向下復(fù)制。解決了主從的不足
? ? ? ? ?master --> slave1 --> slave2 --> slave3
這降低了主節(jié)點(diǎn)的負(fù)載,所以如果master需要掛載多個(gè)slave時(shí),而我們又想保證master的穩(wěn)定性,就可以采用這種拓?fù)浣Y(jié)構(gòu)。
????而springboot內(nèi)嵌的Luttuce鏈接池,默認(rèn)不會(huì)改變r(jià)edis集群的拓?fù)浣Y(jié)構(gòu),當(dāng)我們在超時(shí)時(shí)間觸發(fā)后,springboot與redis的鏈接就斷開了,而下次訪問時(shí)還會(huì)訪問這個(gè)已經(jīng)超時(shí)的拓?fù)涔?jié)點(diǎn),導(dǎo)致訪問不成功。這就是springboot操作redis偶爾會(huì)timeout的問題原因所在。
????解決方案呢有很多,大致就是及時(shí)更新這個(gè)拓?fù)涔?jié)點(diǎn)就好了,目前可以通過在Luttuce留下的鉤子函數(shù)中去自定義刷新的拓?fù)溥壿嫞热鐔⒁粋€(gè)cron定時(shí)任務(wù)去做一個(gè)心跳檢測去驗(yàn)證鏈接是否異常來完成。
第一種方案:

springboot中預(yù)留的鉤子函數(shù)中執(zhí)行的customize的方法。
在springboot預(yù)留的接口中自定義類實(shí)現(xiàn)LettuceClientConfigurationBuilderCustomizer接口,實(shí)現(xiàn)customize方法,其中開啟自定義刷新和定時(shí)刷新,而springboot實(shí)現(xiàn)的鉤子函數(shù)中會(huì)自動(dòng)去執(zhí)行customize方法
/*** 通過ClusterTopologyRefreshOptions 開啟定時(shí)刷新和自適應(yīng)刷新* @author azh*/public class MyLuttuceCronTask implements LettuceClientConfigurationBuilderCustomizer {public void customize(org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigurationBuilder) {// ClusterTopologyRefreshOptions 用于開啟自適應(yīng)刷新和定時(shí)刷新 防止timeout 錯(cuò)誤ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()// 開始自適應(yīng)刷新.enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT, ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS).enableAllAdaptiveRefreshTriggers().adaptiveRefreshTriggersTimeout(Duration.ofSeconds(10))//開啟定時(shí)刷新 時(shí)間間隔自己定義.enablePeriodicRefresh(Duration.ofSeconds(15)).build();clientConfigurationBuilder.clientOptions(ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build());}}

第二種方案:
??? Lettuce提供了鏈接校驗(yàn)的方法,只是默認(rèn)沒有開啟,我們可以開啟它,然后通過一個(gè)定時(shí)任務(wù)來解決這個(gè)timeout的問題。
/*** 開啟獲取鏈接的校驗(yàn)* @author azh*/public class GetLettuceConnection implements InitializingBean {private RedisConnectionFactory redisConnectionFactory;public void afterPropertiesSet() throws Exception {if (redisConnectionFactory instanceof LettuceConnectionFactory) {LettuceConnectionFactory factory = (LettuceConnectionFactory) redisConnectionFactory;factory.setValidateConnection(true);}}}/*** 每隔2s校驗(yàn)lettuce是否異常,解決lettuce因?yàn)椴桓聄edis拓?fù)浣Y(jié)構(gòu)導(dǎo)致netty無法及時(shí)監(jiān)控到導(dǎo)致timeout的問題* @author azh*/public class RedisCronTask {private RedisConnectionFactory redisConnectionFactory;// 每2s執(zhí)行一下(cron = "0/2 * * * * ?")public void task() {if (redisConnectionFactory instanceof LettuceConnectionFactory) {LettuceConnectionFactory factory = (LettuceConnectionFactory) redisConnectionFactory;factory.validateConnection();}}}
開啟獲取鏈接的校驗(yàn)

定時(shí)任務(wù)去校驗(yàn)luttuce是否異常

???? 最后其實(shí)還有個(gè)比較簡單但頭疼的解決方案,那就是每隔一段時(shí)間都往redis的某個(gè)數(shù)據(jù)庫中去set一個(gè)值,不過這種方案會(huì)耗費(fèi)網(wǎng)絡(luò)資源,但耗費(fèi)也不是太大,算是一種中肯的解決辦法吧。
