上個月線上生產環境有幾個介面出現異常響應,查看生產日誌後發現,如下錯誤 線上Redis客戶端使用的是SpringBoot預設的Lettuce客戶端,並且沒有指定連接池,connection reset by peer這個錯誤是當前客戶端連接在不知情的情況下被服務端斷開後產生,也就是說當前客戶端Red ...
上個月線上生產環境有幾個介面出現異常響應,查看生產日誌後發現,如下錯誤
線上Redis客戶端使用的是SpringBoot
預設的Lettuce
客戶端,並且沒有指定連接池,connection reset by peer
這個錯誤是當前客戶端連接在不知情的情況下被服務端斷開後產生,也就是說當前客戶端Redis連接已經在服務端斷開了,但是客戶端並不知道,當請求進來時,Lettuce
繼續使用當前Redis連接請求數據時,就會提示connection reset by peer
。
一般情況下服務端斷開連接都會發送FIN
包通知客戶端,但是當我在用tcpdump
監控服務端tcp傳輸後,發現Redis服務端tcp連接在無活動一段時間,比如10分鐘後會收到來自客戶端的RST
包,然而我的客戶端也在使用wireshark抓包中,並沒有發送給服務端RST
包,這就很奇怪了,猜測這裡是可能是伺服器對tcp連接的限制導致,對長時間無活動的tcp連接強制斷開處理。所以這裡線上環境Redis連接偶爾產生connection reset by peer
錯誤是被我復現出來了。
既然這裡知道是Redis連接長時間無活動後被斷開導致的bug,那怎麼解決?
博主一開始以為重試可以解決,但是發現事情沒有想象的簡單。上代碼
// 查詢Redis
public <T> T getCacheObject(final String key) {
try {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return retryGetCacheObject(key, 3);
}
}
// 重試查詢Redis
public <T> T retryGetCacheObject(final String key, int retryCount) {
try {
log.info("retryGetCacheObject, key:{}, retryCount:{}", key, retryCount);
if (retryCount <= 0) {
return null;
}
Thread.sleep(200L);
retryCount--;
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return retryGetCacheObject(key, retryCount);
}
}
上面代碼的意思是第一次查詢Redis發生異常後,每隔200毫秒在查3次。當實際運行時,發現這裡會提示三次connection reset by peer
錯誤,一直沒有取到新的Redis連接。
到這裡這個問題的我的解決思路其實就是怎麼在Redis連接發生異常後,怎麼創建一條新的連接進行代替。
不多說直接上代碼:
// Lettuce連接工廠
@Autowired
private LettuceConnectionFactory lettuceConnectionFactory;
/**
* 獲得緩存的基本對象。
*
* @param key 緩存鍵值
* @return 緩存鍵值對應的數據
*/
public <T> T getCacheObject(final String key) {
try {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return retryGetCacheObject(key, 1);
}
}
public <T> T retryGetCacheObject(final String key, int retryCount) {
try {
log.info("retryGetCacheObject, key:{}, retryCount:{}", key, retryCount);
if (retryCount <= 0) {
return null;
}
lettuceConnectionFactory.resetConnection();
Thread.sleep(200L);
retryCount--;
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return retryGetCacheObject(key, retryCount);
}
}
在用當前Redis連接獲取數據發生異常超過timeout
間隔後,拋出異常,進入重試方法,使用 lettuceConnectionFactory.resetConnection()
方法進行連接重置,創建一條新的連接後,繼續獲取數據,從而正常響應客戶端。lettuceConnectionFactory
對象是對Lettuce
無池化連接的工廠實現,提供了 lettuceConnectionFactory.getConnection(); lettuceConnectionFactory.initConnection(); lettuceConnectionFactory.resetConnection();
等獲取、初始化、重置連接的方法
配合springboot
配置timeout
將獲取數據的超時時間設置為2秒,從而將介面請求耗時也控制在2秒左右
redis:
xx: xx
timeout: 2000
到此生產環境這裡SpringBoot
項目下Lettuce
客戶端無池化連接偶爾斷開的bug算是解決了
最後貼一下實戰項目地址newbeemall,newbee-mall商城的mybatis plus版本 實現了優惠卷領取, 支付寶沙箱支付,後臺添加搜索,RedisSearch分詞檢索