Individuazione e backoff esponenziale dei client del cluster (Valkey e Redis OSS) - Amazon ElastiCache

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Individuazione e backoff esponenziale dei client del cluster (Valkey e Redis OSS)

Quando ci si connette a un cluster ElastiCache Valkey o Redis OSS in modalità cluster abilitata, la libreria client corrispondente deve essere in grado di riconoscere il cluster. I client devono ottenere una mappa degli slot hash ai nodi corrispondenti del cluster per inviare le richieste ai nodi giusti ed evitare il sovraccarico delle prestazioni dovuto alla gestione dei reindirizzamenti del cluster. Di conseguenza, il client deve ottenere l'elenco completo degli slot e dei nodi mappati in due diverse situazioni:

  • Il client è inizializzato e deve compilare la configurazione iniziale degli slot.

  • Il server riceve un reindirizzamento MOVED, ad esempio in caso di failover in cui tutti gli slot serviti dal precedente nodo primario vengono acquisiti dalla replica oppure in caso di ripartizionamento quando gli slot vengono spostati dal nodo primario di origine al nodo primario di destinazione.

L'individuazione dei client viene in genere effettuata tramite l'emissione di un comando CLUSTER SLOT o CLUSTER NODE al server Valkey o Redis OSS. Consigliamo il metodo CLUSTER SLOT perché restituisce al client il set di intervalli di slot e i nodi primari e di replica associati. Questo metodo non richiede un'analisi aggiuntiva da parte del client ed è più efficiente.

A seconda della topologia del cluster, la dimensione della risposta per il comando CLUSTER SLOT può variare in base alla dimensione del cluster. I cluster più grandi con più nodi producono una risposta di dimensione maggiore. Di conseguenza, è importante garantire che il numero di client che eseguono l'individuazione della topologia del cluster non cresca in modo illimitato. Ad esempio, quando l'applicazione client avvia o perde la connessione del server e deve eseguire l'individuazione del cluster, un errore comune è che l'applicazione client generi diverse richieste di riconnessione e individuazione senza aggiungere un backoff esponenziale al momento del nuovo tentativo. Ciò può impedire al server Valkey o Redis OSS di rispondere per un periodo di tempo prolungato, con un utilizzo della CPU al 100%. L'interruzione si prolunga se ogni comando CLUSTER SLOT deve elaborare un numero elevato di nodi nel bus del cluster. In passato abbiamo osservato diverse interruzioni dei client a causa di questo comportamento in diversi linguaggi, tra cui Python redis-py-cluster () e Java (Lettuce e Redisson).

In una cache serverless, molti problemi vengono mitigati automaticamente perché la topologia del cluster pubblicizzata è statica ed è composta da due elementi: un endpoint di scrittura e un endpoint di lettura. L'individuazione del cluster viene inoltre distribuita automaticamente su più nodi quando si utilizza l'endpoint di cache. I seguenti suggerimenti sono tuttavia utili.

Per mitigare l'impatto causato da un afflusso improvviso di richieste di connessione e individuazione, ti consigliamo di procedere come segue:

  • Implementa un pool di connessioni client di dimensioni limitate per contenere il numero di connessioni simultanee in entrata dell'applicazione client.

  • Quando il client si disconnette dal server a causa di un timeout, riprova con un backoff esponenziale con jitter. In tal modo eviti che più client sovraccarichino il server contemporaneamente.

  • Utilizza le indicazioni contenute in Individuazione degli endpoint di connessione in ElastiCache per trovare l'endpoint del cluster ed eseguire l'individuazione del cluster. In questo modo, distribuisci il carico dell'individuazione su tutti i nodi del cluster (fino a 90) invece di usare i pochi nodi di inizializzazione codificati nel cluster.

Di seguito sono riportati alcuni esempi di codice per la logica esponenziale dei tentativi di backoff in redis-py e Lettuce. PHPRedis

Esempio 1 di logica di backoff: redis-py

redis-py ha un meccanismo di ripetizione dei tentativi integrato che ripete il tentativo una volta subito dopo l'errore. Questo meccanismo può essere abilitato tramite l'retry_on_timeoutargomento fornito durante la creazione di un oggetto Redis OSS. Di seguito è illustrato un meccanismo di ripetizione dei tentativi personalizzato con backoff esponenziale e jitter. Inviamo una richiesta pull per implementare in modo nativo il backoff esponenziale in redis-py (#1494). In futuro potrebbe non essere necessario implementarlo manualmente.

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

Quindi puoi utilizzare il codice seguente per impostare un valore:

client = redis.Redis(connection_pool=redis.BlockingConnectionPool(host=HOST, max_connections=10)) res = run_with_backoff(lambda: client.set("key", "value")) print(res)

A seconda del carico di lavoro, è possibile modificare il valore di backoff di base da 1 secondo a poche decine o centinaia di millisecondi per i carichi di lavoro sensibili alla latenza.

Esempio di logica di backoff 2: PHPRedis

PHPRedis dispone di un meccanismo di riprova integrato che riprova un massimo di 10 volte (non configurabile). Si verifica un ritardo configurabile tra i tentativi (con un jitter dal secondo tentativo in poi). Per ulteriori informazioni, consulta il seguente codice di esempio. Abbiamo inviato una pull request per implementare nativamente il backoff esponenziale in PHPredis (#1986), che da allora è stata unita e documentata. Per chi utilizza l'ultima versione di PHPRedis, non sarà necessario implementarlo manualmente, ma abbiamo incluso qui il riferimento per quelli delle versioni precedenti. Il seguente è un esempio di codice che configura il ritardo del meccanismo di ripetizione dei tentativi:

$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);

Esempio 3 di logica di backoff: Lettuce

Lettuce dispone di meccanismi di ripetizione dei tentativi incorporati basati sulle strategie di backoff esponenziale descritte nel post Exponential Backoff and Jitter. Di seguito è riportato un estratto di codice che mostra l'approccio completo del jitter:

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(); } } }