Découverte des clients du cluster et ralentissement exponentiel (Valkey et Redis) OSS - Amazon ElastiCache

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Découverte des clients du cluster et ralentissement exponentiel (Valkey et Redis) OSS

Lorsque vous vous connectez à un OSS cluster ElastiCache Valkey ou Redis en mode cluster activé, la bibliothèque cliente correspondante doit être consciente du cluster. Les clients doivent obtenir une carte des emplacements de hachage sur les nœuds correspondants du cluster afin d’envoyer des demandes aux nœuds appropriés et d’éviter la surcharge de performances liée à la gestion des redirections du cluster. Par conséquent, le client doit découvrir la liste complète des emplacements et des nœuds mappés dans deux cas différents :

  • Le client est initialisé et doit renseigner la configuration initiale des emplacements.

  • Une MOVED redirection est reçue du serveur, par exemple en cas de basculement lorsque tous les emplacements desservis par l'ancien nœud principal sont pris en charge par la réplique, ou en cas de repartitionnement lorsque les emplacements sont déplacés du nœud principal source vers le nœud principal cible

La découverte du client se fait généralement en émettant une CLUSTER NODE commande CLUSTER SLOT ou au serveur Valkey ou RedisOSS. Nous recommandons CLUSTER SLOT cette méthode car elle renvoie au client l'ensemble des plages de slots ainsi que les nœuds principaux et répliques associés. Cette méthode ne nécessite pas d’analyse supplémentaire de la part du client. Elle est également plus efficace.

En fonction de la topologie du cluster, la taille de la réponse à la CLUSTER SLOT commande peut varier en fonction de la taille du cluster. Les clusters de plus grande taille dotés d’un plus grand nombre de nœuds produisent une réponse plus longue. Par conséquent, il est important de veiller à ce que le nombre de clients qui effectuent la découverte de la topologie du cluster n’augmente pas de manière illimitée. Par exemple, lorsque l’application client démarre ou perd la connexion avec le serveur et doit effectuer une découverte de clusters, une erreur courante est qu’elle lance plusieurs demandes de reconnexion et de découverte sans ajouter de backoff exponentiel lors d’une nouvelle tentative. Cela peut empêcher le OSS serveur Valkey ou Redis de répondre pendant une période prolongée, avec une CPU utilisation à 100 %. La panne est prolongée si chaque CLUSTER SLOT commande doit traiter un grand nombre de nœuds dans le bus du cluster. Nous avons observé plusieurs pannes de clients dans le passé en raison de ce comportement dans différents langages, notamment Python (redis-py-cluster) et Java (Lettuce et Redisson).

Dans un cache sans serveur, de nombreux problèmes sont automatiquement atténués, car la topologie du cluster annoncée est statique et comprend deux entrées : un point de terminaison d’écriture et un point de terminaison de lecture. De plus, la découverte de clusters est automatiquement répartie sur plusieurs nœuds lors de l’utilisation du point de terminaison du cache. Les recommandations suivantes restent toutefois utiles.

Pour atténuer l’impact provoqué par un afflux soudain de demandes de connexion et de découverte, nous recommandons ce qui suit :

  • Implémentez un groupe de connexions client de taille limitée pour limiter le nombre de connexions entrantes simultanées en provenance de l’application client.

  • Lorsque le client se déconnecte du serveur en raison d’un délai d’expiration, réessayez en ajoutant un backoff exponentiel avec instabilité. Cela permet d’éviter que plusieurs clients ne surchargent le serveur en même temps.

  • Consultez Recherche de points de terminaison de connexion dans ElastiCache pour trouver le point de terminaison de cluster afin d’effectuer la découverte de clusters. En agissant ainsi, vous répartissez la charge de découverte sur tous les nœuds du cluster (jusqu’à 90) au lieu de toucher quelques nœuds de départ codés en dur du cluster.

Voici quelques exemples de code pour la logique de réessai exponentielle dans redis-py,, et Lettuce. PHPRedis

Exemple de logique avec backoff 1 : redis-py

redis-py possède un mécanisme de nouvelle tentative intégré qui permet d’effectuer une nouvelle tentative immédiatement après un échec. Ce mécanisme peut être activé via l'retry_on_timeoutargument fourni lors de la création d'un OSS objet Redis. L’exemple ci-dessous illustre un mécanisme de nouvelle tentative personnalisé avec backoff exponentiel et instabilité. Nous avons soumis une demande d’extraction pour implémenter de manière native le backoff exponentiel dans redis-py (#1494). À l’avenir, il ne sera peut-être plus nécessaire de procéder à une implémentation manuelle.

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

Vous pouvez ensuite utiliser le code suivant pour définir une valeur :

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

En fonction de votre charge de travail, vous souhaiterez peut-être modifier la valeur de backoff de base de 1 seconde à quelques dizaines ou centaines de millisecondes pour les charges de travail sensibles à la latence.

Exemple de logique de rétrogradation 2 : PHPRedis

PHPRedispossède un mécanisme intégré qui permet de réessayer 10 fois (non configurable) au maximum. Il est possible de configurer un délai entre les tentatives (avec une instabilité à partir de la deuxième tentative). Pour plus d’informations, consultez l’exemple de code ci-après. Nous avons soumis une pull request pour implémenter nativement le backoff exponentiel dans PHPredis(#1986) qui a depuis été fusionnée et documentée. Pour ceux qui utilisent la dernière version dePHPRedis, il ne sera pas nécessaire de l'implémenter manuellement, mais nous avons inclus ici la référence pour ceux des versions précédentes. L’exemple de code suivant permet de configurer le délai du mécanisme de nouvelle tentative :

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

Exemple de logique avec backoff 3 : Lettuce

Lettuce possède des mécanismes de nouvelle tentative intégrés basés sur les stratégies de backoff exponentiel décrites dans l’article Backoff exponentiel et instabilité (langue française non garantie). Voici un extrait de code qui illustre l’approche avec instabilité complète :

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