0 前言 潛心打造國內一流,國際領先的技術乾貨。 文章收錄在我的 GitHub 倉庫,歡迎Star/fork: JavaEdge-Interview 受網路和運行環境影響,應用程式可能遇到暫時性故障,如瞬時網路抖動、服務暫時不可用、服務繁忙導致超時等。 自動重試機制可大幅避免此類故障,保障操作成功執 ...
0 前言
潛心打造國內一流,國際領先的技術乾貨。
文章收錄在我的 GitHub 倉庫,歡迎Star/fork:
受網路和運行環境影響,應用程式可能遇到暫時性故障,如瞬時網路抖動、服務暫時不可用、服務繁忙導致超時等。
自動重試機制可大幅避免此類故障,保障操作成功執行。
1 引發暫時性故障的原因
1.1 故障觸發了高可用機制
雲Redis支持節點健康狀態監測,當監測到實例中的主節點不可用時,會自動觸發主備切換,例如將主節點和從節點進行互換,保障實例的高可用性。此時,客戶端可能會遇到下列暫時性故障:秒級的連接閃斷。30秒內的只讀狀態(用於避免主備切換引起潛在的數據丟失風險和雙寫)。
更多參見:主備切換。
1.2 慢查詢引起了請求堵塞
執行時間複雜度為O(N)的操作,引發慢查詢和請求的堵塞,此時,客戶端發起的其他請求可能出現暫時性失敗。
1.3 複雜的網路環境
由於客戶端與Redis伺服器之間複雜網路環境引起,可能出現偶發的網路抖動、數據重傳等問題,此時,客戶端發起的請求可能會出現暫時性失敗。
2 推薦的重試準則
2.1 僅重試冪等的操作
由於超時可能發生在下述任一階段:該命令由客戶端發送成功,但尚未到達Redis。命令到達Redis,但執行超時。命令在Redis中執行結束,但結果返回給客戶端時發生超時。如果執行重試可能導致某個操作在Redis中被重覆執行,因此不是所有操作均適合設計重試機制。通常推薦僅重試冪等的操作,例如SET操作,即多次執行SET a b命令,那麼a的值只可能是b或執行失敗;如果執行LPUSH mylist a則不是冪等的,可能導致mylist中包含多個a元素。
2.2 適當的重試次數與間隔
根據業務需求和實際場景調整適當的重試次數與間隔,否則可能引發下述問題:如果重試次數不足或間隔太長,應用程式可能無法完成操作而導致失敗。如果重試次數過大或間隔過短,應用程式可能會占用過多的系統資源,且可能因請求過多而堵塞在伺服器上無法恢復。常見的重試間隔方式包括立即重試、固定時間重試、指數增加時間重試、隨機時間重試等。
2.3 避免重試嵌套
避免重試嵌套,否則可能會導致重覆的重試且無法停止。
2.4 記錄重試異常並列印失敗報告
在重試過程中,建議在WARN級別上列印重試錯誤日誌,同時,僅在重試失敗時列印異常信息。
3 Jedis
建議使用Jedis 4.0.0及以上版本,推薦使用最新的Jedis版本,以下代碼為Jedis 5.0.0的重試示例。
3.1 添加Jedis的Pom依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.0</version>
</dependency>
3.2 重試實戰
① 標準架構實例或集群架構代理(Proxy)模式
使用JedisPool模式。
該示例會將SET命令自動重試5次,且總重試時間不超過10s,每次重試之間等待類指數間隔的時間,如果最終不成功,則拋出異常。
PooledConnectionProvider provider = new PooledConnectionProvider(HostAndPort.from("127.0.0.1:6379"));
int maxAttempts = 5; // 最大重試次數
Duration maxTotalRetriesDuration = Duration.ofSeconds(10); // 最大的重試時間
UnifiedJedis jedis = new UnifiedJedis(provider, maxAttempts, maxTotalRetriesDuration);
try {
System.out.println("set key: " + jedis.set("key", "value"));
} catch (Exception e) {
// 表示嘗試maxAttempts次或到達了最大查詢時間maxTotalRetriesDuration仍舊沒有訪問成功。
e.printStackTrace();
}
② 集群架構直連模式
使用JedisCluster模式。
可以通過配置maxAttempts參數來定義失敗情況下的重試次數,預設值為5,如果最終不成功,則拋出異常。
HostAndPort hostAndPort = HostAndPort.from("127.0.0.1:30001");
int connectionTimeout = 5000;
int soTimeout = 2000;
int maxAttempts = 5;
ConnectionPoolConfig config = new ConnectionPoolConfig();
JedisCluster jedisCluster = new JedisCluster(hostAndPort, connectionTimeout, soTimeout, maxAttempts, config);
try {
System.out.println("set key: " + jedisCluster.set("key", "value"));
} catch (Exception e) {
// 表示嘗試maxAttempts之後仍舊沒有訪問成功。
e.printStackTrace();
}
4 Redisson
Redisson客戶端提供了兩個參數來控制重試邏輯:
- retryAttempts:重試次數,預設為3。
- retryInterval:重試間隔,預設為1,500毫秒。
重試示例如下:
Config config = new Config();
config.useSingleServer()
.setTimeout(1000)
.setRetryAttempts(3)
.setRetryInterval(1500) //ms
.setAddress("redis://127.0.0.1:6379");
RedissonClient connect = Redisson.create(config);
5 StackExchange.Redis
StackExchang.Redis客戶端目前僅支持重試時連接,重試示例如下:
var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,connectRetry=3");
說明
如需實現API級別的重試策略,請參見Polly。
6 Lettuce
Lettuce客戶端未提供在命令超時後重試的參數,但是您可以通過下述參數來實現命令重試策略:
- at-most-once execution:命令最多執行1次,即0次或1次,如果連接斷開並重新連接,命令可能會丟失。
- at-least-once execution(預設):最少成功執行1次,即可能會在執行時進行多次嘗試,保障最少成功執行1次。使用此策略時,如果Tair實例發生了主備切換,此時客戶端可能累積了較多的重試命令,主備切換完成後可能會引發Tair實例的CPU使用率激增。
說明
更多信息,請參見Client-Options和Command execution reliability。
重試示例:
clientOptions.isAutoReconnect() ? Reliability.AT_LEAST_ONCE : Reliability.AT_MOST_ONCE;
參考:
本文由博客一文多發平臺 OpenWrite 發佈!