Lettuce 用戶端組態 (Valkey 和 Redis OSS) - Amazon ElastiCache

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

Lettuce 用戶端組態 (Valkey 和 Redis OSS)

本節說明建議的 Java 和 Lettuce 組態選項,以及它們如何套用至 ElastiCache 叢集。

本節中的建議已使用 Lettuce 版本 6.2.2 進行測試。

Java DNS 快取 TTL

Java 虛擬機器 (JVM) 會快取 DNS 名稱查詢。當 JVM 將主機名稱解析為 IP 位址時,它會在指定的時間段內快取 IP 位址,稱為存留時間 (TTL)。

選擇 TTL 值是為了在延遲和對變化的回應能力之間進行權衡。DNS 解析程式使用較短的 TTL,可以更快地注意到叢集 DNS 中的更新。這可讓您的應用程式更快回應叢集所經歷的替換或其他工作流程。不過,如果 TTL 太低,則會增加查詢量,進而增加應用程式的延遲時間。雖然沒有正確的 TTL 值,但在設定 TTL 值時,值得考量可以等待變更生效的時間長度。

由於 ElastiCache 節點使用可能會變更的 DNS 名稱項目,因此我們建議您將 JVM 設定為 5 到 10 秒的低 TTL。這可確保當節點的 IP 地址變更時,您的應用程式將可透過重新查詢 DNS 項目來接收並使用資源的新 IP 地址。

在一些 Java 組態上,JVM 的預設 TTL 會如此設定,在重新啟動 JVM 之前,「絕不」重新整理 DNS 項目。

如需如何設定 JVM TTL 的詳細資訊,請參閱如何設定 JVM TTL

Lettuce 版本

建議使用 Lettuce 6.2.2 或更新版本。

端點

當您使用已啟用叢集模式的叢集時,請將 redisUri 設為叢集組態端點。此 URI 的 DNS 查閱會傳回叢集中所有可用節點的清單,並在叢集初始化期間隨機解析到其中之一。如需拓撲重新整理如何運作的詳細資訊,請參閱本主題稍後的 dynamicRefreshResources

SocketOption

啟用 KeepAlive。啟用此選項可減少在命令執行期間處理失敗連線的需求。

請務必根據應用程式需求和工作負載設定連線逾時。如需詳細資訊,請參閱本主題稍後的 Timeouts (逾時) 章節。

ClusterClientOption:已啟用叢集模式的用戶端選項

當連線中斷時,啟用 AutoReconnect

設置 CommandTimeout。如需更多詳細資料,請參閱本主題稍後的 Timeouts (逾時) 章節。

設定 nodeFilter 以篩選出拓樸中故障的節點。Lettuce 會將「叢集節點」輸出中找到的所有節點 (包含具有 PFAIL/FAIL 狀態的節點) 儲存在用戶端的「分割區」(也稱為碎片) 中。在建立叢集拓撲的過程中,它會嘗試連線到所有的分割區節點。當節點因任何原因被取代時,新增故障節點的這種 Lettuce 行為可能會導致連線錯誤 (或警告)。

例如,在容錯移轉完成且叢集啟動復原程序之後,clusterTopology 正在重新整理時,叢集匯流排節點對應的時間很短,在完全從拓撲中移除之前,向下節點會列為 FAIL 節點。在此期間,Lettuce 用戶端會將其視為運作狀態良好的節點,並持續與其連線。這會在重試耗盡後導致失敗。

例如:

final ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder() ... // other options .nodeFilter(it -> ! (it.is(RedisClusterNode.NodeFlag.FAIL) || it.is(RedisClusterNode.NodeFlag.EVENTUAL_FAIL) || it.is(RedisClusterNode.NodeFlag.HANDSHAKE) || it.is(RedisClusterNode.NodeFlag.NOADDR))) .validateClusterNodeMembership(false) .build(); redisClusterClient.setOptions(clusterClientOptions);
注意

節點篩選最適合用於 DynamicRefreshSources 設為 true 時。否則,如果拓撲檢視是從單一有問題的種子節點中取得,並將某些碎片的主節點視為故障,則它將篩除此主節點,這將導致槽不被涵蓋。擁有多個種子節點 (DynamicrefreshSources 為真時) 可降低發生此問題的可能性,因為在使用新升級的主節點進行容錯移轉之後,至少部分種子節點應具有更新的拓撲檢視。

ClusterTopologyRefreshOptions:用於控制啟用叢集模式的用戶端的叢集拓撲重新整理選項

注意

已停用叢集模式的叢集不支援叢集探索命令,也不相容於所有用戶端動態拓撲探索功能。

ElastiCache 的停用叢集模式與 Lettuce 的 MasterSlaveTopologyRefresh 不相容。相反地,對於停用的叢集模式,您可以設定 StaticMasterReplicaTopologyProvider 並提供叢集讀取和寫入端點。

如需連接至已停用叢集模式之叢集的詳細資訊,請參閱 尋找 Valkey 或 Redis OSS (停用叢集模式) 叢集的端點 (主控台)

如果您想要使用 Lettuce 的動態拓撲探索功能,可以使用與現有叢集相同的碎片組態來建立已啟用叢集模式的叢集。不過,對於已啟用叢集模式的叢集,建議至少設定 3 個具有至少 1 個複本的碎片,以支援快速容錯移轉。

啟用 enablePeriodicRefresh。這會啟用定期叢集拓撲更新,以便用戶端以重新整理期間的間隔更新叢集拓撲 (預設值:60 秒)。停用時,用戶端只有在嘗試對叢集執行命令發生錯誤時,叢集拓撲才會更新。

啟用此選項後,您可以將此工作新增至背景任務,以減少與重新整理叢集拓撲相關的延遲。雖然拓撲重新整理是在背景工作中執行,但對於具有許多節點的叢集而言,可能會有些慢。這是因為所有節點都在查詢其檢視以取得最新的叢集檢視。如果您執行的是大型叢集,則可能需要增加期間。

啟用 enableAllAdaptiveRefreshTriggers。這會啟用使用所有觸發器的適應性拓樸重新整理:MOVED_REDIRECT, ASK_REDIRECT, PERSISTENT_RECONNECTS, UNCOVERED_SLOT, UNKNOWN_NODE。自適應重新整理觸發會根據 Valkey 或 Redis OSS 叢集操作期間發生的事件啟動拓撲檢視更新。啟用此選項會在上述觸發程序發生時立即重新整理拓撲。適應性觸發程序重新整理會使用逾時來限制速率,因為事件可能會大規模發生 (更新之間的預設逾時:30)。

啟用 closeStaleConnections。如此可在重新整理叢集拓樸時關閉過時的連線。只有在 ClusterTopologyRefreshOptions.isPeriodicRefreshEnabled() 為真時,它才會生效。啟用後,用戶端可以關閉過時的連線,並在背景建立新連線。這可減少在命令執行期間處理失敗連線的需求。

啟用 dynamicRefreshResources。我們建議您為小型叢集啟用 dynamicRefreshResources,並在大型叢集時停用。dynamicRefreshResources 可讓您從提供的種子節點 (例如,叢集組態端點) 探索叢集節點。它會使用所有探索到的節點做為重新整理叢集拓撲的來源。

使用動態重新整理查詢所有探索到的叢集拓撲節點,並嘗試選擇最準確的叢集檢視。如果其設定為 false,則只會使用初始種子節點做為拓撲探索的來源,而且只會取得初始種子節點的用戶端數量。在停用時,如果叢集配置端點解析為故障的節點,則嘗試重新整理叢集檢視會失敗,並導致例外狀況。這種情況可能會發生,因為從叢集組態端點移除故障節點的項目需要一些時間。因此,組態端點仍可在短時間內隨機解析為故障的節點。

但是,在其啟用後,我們會使用從叢集檢視接收到的所有叢集節點,來查詢其目前的檢視。因為我們會從該檢視中篩選出故障的節點,所以拓撲重新整理將會成功。不過,當動態重新整理來源為真時,Lettuce 會查詢所有節點以取得叢集檢視,然後比較結果。因此,對於具有大量節點的叢集來說,它可能很昂貴。建議您在多節點的叢集時關閉此功能。

final ClusterTopologyRefreshOptions topologyOptions = ClusterTopologyRefreshOptions.builder() .enableAllAdaptiveRefreshTriggers() .enablePeriodicRefresh() .dynamicRefreshSources(true) .build();

ClientResources

使用 DirContextDnsResolver 設定 DnsResolver。該 DNS 解析程式係以 Java 的 com.sun.jndi.dns.DnsContextFactory 為基礎。

使用指數退避和完全抖動來設定 reconnectDelay。Lettuce 具有以指數退避策略為基礎的內建重試機制。如需詳細資訊,請參閱 AWS 架構部落格上的指數退避和抖動。如需重試退避策略重要性的詳細資訊,請參閱 AWS 資料庫部落格上最佳實務部落格文章的退避邏輯區段。

ClientResources clientResources = DefaultClientResources.builder() .dnsResolver(new DirContextDnsResolver()) .reconnectDelay( Delay.fullJitter( Duration.ofMillis(100), // minimum 100 millisecond delay Duration.ofSeconds(10), // maximum 10 second delay 100, TimeUnit.MILLISECONDS)) // 100 millisecond base .build();

逾時

使用低於指令逾時的連線逾時值。Lettuce 使用延遲連接建立。因此,如果連線逾時高於指令逾時,而 Lettuce 嘗試連線到健康狀態不良的節點,且永遠超過指令逾時,則您可能會在拓撲重新整理後發生一段時間的持續性失敗。

針對不同的指令使用動態指令逾時。我們建議您根據指令預期持續時間設定指令逾時。例如,對重複多個金鑰的指令 (例如 FLUSHDB、FLUSHALL、KEYS、SMEMBERS 或 Lua 指令碼) 使用較長的逾時時間。對於單一金鑰指令 (例如 SET、GET 和 HSET) 使用較短的逾時。

注意

下列範例中設定的逾時適用於執行 SET/GET 指令的測試,且金鑰和值長度最多 20 個位元組。在指令很複雜或金鑰和值較大時,處理時間可能會更長。您應該根據應用程式的使用案例設定逾時。

private static final Duration META_COMMAND_TIMEOUT = Duration.ofMillis(1000); private static final Duration DEFAULT_COMMAND_TIMEOUT = Duration.ofMillis(250); // Socket connect timeout should be lower than command timeout for Lettuce private static final Duration CONNECT_TIMEOUT = Duration.ofMillis(100); SocketOptions socketOptions = SocketOptions.builder() .connectTimeout(CONNECT_TIMEOUT) .build(); class DynamicClusterTimeout extends TimeoutSource { private static final Set<ProtocolKeyword> META_COMMAND_TYPES = ImmutableSet.<ProtocolKeyword>builder() .add(CommandType.FLUSHDB) .add(CommandType.FLUSHALL) .add(CommandType.CLUSTER) .add(CommandType.INFO) .add(CommandType.KEYS) .build(); private final Duration defaultCommandTimeout; private final Duration metaCommandTimeout; DynamicClusterTimeout(Duration defaultTimeout, Duration metaTimeout) { defaultCommandTimeout = defaultTimeout; metaCommandTimeout = metaTimeout; } @Override public long getTimeout(RedisCommand<?, ?, ?> command) { if (META_COMMAND_TYPES.contains(command.getType())) { return metaCommandTimeout.toMillis(); } return defaultCommandTimeout.toMillis(); } } // Use a dynamic timeout for commands, to avoid timeouts during // cluster management and slow operations. TimeoutOptions timeoutOptions = TimeoutOptions.builder() .timeoutSource( new DynamicClusterTimeout(DEFAULT_COMMAND_TIMEOUT, META_COMMAND_TIMEOUT)) .build();