本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
叢集用戶端探索和指數退避 (Valkey 和 RedisOSS)
在啟用叢集模式下連線至 ElastiCache Valkey 或 Redis OSS叢集時,對應的用戶端程式庫必須具備叢集感知能力。用戶端必須取得雜湊位置與叢集中相對應節點的對應,才能將請求傳送至正確的節點,並避免處理叢集重新導向的額外效能負荷。因此,用戶端必須在兩種不同的情況下,探索位置和所對應節點的完整清單:
用戶端已初始化,且必須填入初始位置組態
從伺服器接收MOVED重新導向,例如當複本接管先前主節點提供的所有插槽時,容錯移轉的情況,或當插槽從來源主節點移至目標主節點時,重新分割
用戶端探索通常透過向 Valkey CLUSTERSLOT或 Redis OSS 伺服器發出 或 CLUSTERNODE命令來完成。建議您使用此CLUSTERSLOT方法,因為它會將一組插槽範圍和相關聯的主要節點和複本節點傳回給用戶端。這不需要從用戶端進行額外剖析,而且較有效率。
根據叢集拓撲,CLUSTERSLOT命令的回應大小可能會根據叢集大小而有所不同。具有較多節點的較大型叢集會產生較大型的回應。因此,請務必確保執行叢集拓撲探索的用戶端數量不會無限增加。例如,當用戶端應用程式啟動或中斷來自伺服器的連線,而且必須執行叢集探索時,常見的錯誤是,用戶端應用程式發出數個重新連線和探索請求,但未在重試時加上指數退避。這可能會導致 Valkey 或 Redis OSS 伺服器長時間沒有回應,使用CPU率為 100%。如果每個CLUSTERSLOT命令必須處理叢集匯流排中的大量節點,則中斷時間會延長。我們在過去觀察到多個用戶端中斷,因為此行為涉及多種不同語言,包括 Python (redis-py-cluster) 和 Java (Lettuce 和 Redisson)。
在無伺服器快取中,許多問題會自動緩解,因為公告的叢集拓撲是靜態的,並且由兩個項目組成:寫入端點和讀取端點。使用快取端點時,叢集探索也會自動分散到多個節點上。不過,以下建議仍很實用。
為了減輕突然湧入連線和探索請求所造成的影響,以下是我們的建議做法:
實作具有大小限制的用戶端連線集區,以限制來自用戶端應用程式的並行傳入連線數。
當用戶端因逾時而中斷與伺服器的連線時,請使用指數退避和抖動進行重試。這樣有助於避免多個用戶端同時癱瘓伺服器。
使用位於 在 中尋找連線端點 ElastiCache 的指南尋找叢集端點來執行叢集探索。這樣做就能將探索負載分散到叢集中的所有節點 (最多 90 個),而不會集中在叢集中少數幾個硬式編碼的種子節點。
以下是 redis-py、 PHPRedis和 Lettuce 中指數退避重試邏輯的一些程式碼範例。
退避邏輯範例 1:redis-py
redis-py 有內建的重試機制,會在失敗後立即重試一次。此機制可以透過建立 Redis OSSretry_on_timeout
引數來啟用。我們在這裡示範搭配指數退避和抖動的自動重試機制。我們已提交了提取請求,以在 redis-py (#1494)
def run_with_backoff(function, retries=5): base_backoff = 0.1 # base 100ms backoff max_backoff = 10 # sleep for maximum 10 seconds tries = 0 while True: try: return function() except (ConnectionError, TimeoutError): if tries >= retries: raise backoff = min(max_backoff, base_backoff * (pow(2, tries) + random.random())) print(f"sleeping for {backoff:.2f}s") sleep(backoff) tries += 1
您可以使用下面的程式碼來設定值:
client = redis.Redis(connection_pool=redis.BlockingConnectionPool(host=HOST, max_connections=10)) res = run_with_backoff(lambda: client.set("key", "value")) print(res)
根據您的工作負載而定,您可能需要針對延遲敏感的工作負載,將基本退避值從 1 秒變更為數十或數百毫秒。
退避邏輯範例 2: PHPRedis
PHPRedis 具有內建重試機制,可重試 (不可設定) 最多 10 次。您可設定兩次重試之間的延遲 (從第二次重試開始使用抖動)。如需詳細資訊,請參閱下列範例程式碼
$timeout = 0.1; // 100 millisecond connection timeout $retry_interval = 100; // 100 millisecond retry interval $client = new Redis(); if($client->pconnect($HOST, $PORT, $timeout, NULL, $retry_interval) != TRUE) { return; // ERROR: connection failed } $client->set($key, $value);
退避邏輯範例 3:Lettuce
Lettuce 採用以指數退避和抖動
public static void main(String[] args) { ClientResources resources = null; RedisClient client = null; try { resources = DefaultClientResources.builder() .reconnectDelay(Delay.fullJitter( Duration.ofMillis(100), // minimum 100 millisecond delay Duration.ofSeconds(5), // maximum 5 second delay 100, TimeUnit.MILLISECONDS) // 100 millisecond base ).build(); client = RedisClient.create(resources, RedisURI.create(HOST, PORT)); client.setOptions(ClientOptions.builder() .socketOptions(SocketOptions.builder().connectTimeout(Duration.ofMillis(100)).build()) // 100 millisecond connection timeout .timeoutOptions(TimeoutOptions.builder().fixedTimeout(Duration.ofSeconds(5)).build()) // 5 second command timeout .build()); // use the connection pool from above example } finally { if (connection != null) { connection.close(); } if (client != null){ client.shutdown(); } if (resources != null){ resources.shutdown(); } } }