Aller au contenu principal

N'importe quel langage, un seul store : comment Zaris parle le protocole Redis

· 8 minutes de lecture
Clustron Team
Ingénierie des systèmes distribués

N'importe quel langage, un seul store

Zaris est un store clé-valeur distribué conçu nativement pour .NET. Ses clients de premier ordre parlent un protocole binaire efficace, et si votre pile est en .NET, c'est le chemin que vous voulez — de véritables API asynchrones, des types .NET, des primitives de coordination, des remplacements directs IDistributedCache et HybridCache.

Mais la plupart des systèmes ne sont pas uniquement en .NET. Il y a un service Python qui fait du traitement de données, un sidecar Go, un exécuteur de tâches Ruby, une passerelle Node. Les réécrire tous vers un nouveau client pour essayer un nouveau store est inenvisageable. Aussi, dans la 2.0.0, chaque nœud Zaris peut également exposer un listener protocole-Redis (RESP2) : pointez un client Redis existant vers lui et utilisez Zaris comme store clé-valeur en remplacement direct, dans n'importe quel langage, sans changer une ligne de code client.

Cet article explique comment ce front-end fonctionne réellement — le modèle de routage, le modèle de données, les modèles de basculement, et exactement quelles commandes sont prises en charge ou non.

Pourquoi mettre un visage Redis sur un store .NET​

La raison honnête est la portée. Redis a un client dans tous les langages, et une génération entière de développeurs connaît déjà son vocabulaire de commandes. Si Zaris peut accepter ces commandes, une équipe polyglotte obtient un store unique, répliqué et hautement disponible auquel chaque service peut parler dès aujourd'hui — tandis que les services .NET peuvent toujours descendre vers le client natif pour les API typées, les transactions, les baux, les compteurs et le reste.

Le front-end RESP est désactivé par défaut et s'active par nœud, parce qu'il s'agit d'une surface de compatibilité optionnelle, et non du protocole principal. Lorsqu'il est actif, un nœud écoute sur un port Redis et répond aux commandes Redis ordinaires.

Le hachage de base est aligné sur les slots Redis​

Voici la partie qui le fait fonctionner à l'échelle d'un cluster sans proxy au milieu. Zaris hache les clés exactement comme le fait Redis Cluster : CRC16(key) % 16384 donne un slot, et les slots se projettent sur les propres partitions de Zaris. Parce que le routage interne et le calcul de slot de Redis sont identiques, chaque nœud peut annoncer une vue CLUSTER SLOTS / CLUSTER NODES exacte — pour chaque plage de slots, le nœud qui est le propriétaire primaire de la partition de ce slot, c'est-à-dire précisément là où la clé réside.

Ainsi, un client Redis conscient du cluster achemine chaque clé directement vers son propriétaire :

redis client ──CRC16(key) % 16384 → slot → owner──▶ node that holds the key ──▶ reply

Il n'y a pas de proxy et pas de saut supplémentaire. Le client hache la clé, recherche le propriétaire dans la carte de slots qu'il a récupérée au moment de la connexion, et se connecte directement à ce nœud — exactement ce qu'il fait déjà face à un véritable Redis Cluster. Lorsque la commande atterrit sur le nœud propriétaire de la clé, Zaris la sert depuis le store local intra-processus, évitant même un saut en loopback. Lors d'un basculement ou d'un rééquilibrage, le nœud répond par un MOVED, le client rafraîchit sa carte de slots et poursuit. C'est le comportement ordinaire de Redis Cluster, adossé au moteur partitionné de Zaris.

Si vous préférez ne pas exécuter de client conscient du cluster, vous n'y êtes pas obligé — placez les nœuds derrière un load balancer ou utilisez Sentinel à la place, et n'importe quel nœud acceptera n'importe quelle clé. Ces modèles sont décrits ci-dessous.

Le modèle de valeur entière pour les collections​

Zaris 2.0.0 possède des hashes, listes, sets et sorted sets natifs. Via RESP, les commandes de collection (HSET, LPUSH, SADD, ZADD et consorts) se projettent sur ces structures natives. En interne, une mutation est une lecture-modification-écriture sur une valeur entière répliquée : le nœud lit la collection courante, applique votre changement, puis la réécrit avec un compare-and-swap afin que des écrivains concurrents ne puissent pas s'écraser mutuellement. Cela maintient une sémantique correcte à travers la réplication sans nécessiter d'opérations filaires champ par champ, et c'est le même mécanisme qu'utilisent les API natives de structures de données.

Transactions : MULTI/EXEC sur une vraie 2PC​

MULTI/EXEC n'est pas simulé. Une transaction mise en file est exécutée sur la machinerie transactionnelle native à validation en deux phases de Zaris, de sorte que les commandes du bloc sont validées ensemble. Les commandes de compteur se comportent correctement à l'intérieur d'une transaction, elles aussi — INCR, DECR, INCRBY et DECRBY lisent, calculent et écrivent au sein de la transaction plutôt que d'entrer en concurrence en dehors d'elle. Le modèle de verrou distribué SET key val NX PX ttl fonctionne également, ce qui couvre l'usage le plus courant de type Redlock.

Trois façons de basculer​

Parce qu'un cluster Zaris est composé de plusieurs nœuds, un client Redis a besoin d'une stratégie pour ce qu'il doit faire lorsque le nœud auquel il est connecté disparaît. Zaris prend en charge les trois modèles de basculement que les clients Redis comprennent déjà, afin que vous choisissiez celui que votre bibliothèque client et votre infrastructure présupposent déjà :

Load balancer (valeur par défaut recommandée). Placez les nœuds derrière un load balancer TCP et connectez-vous au point de terminaison virtuel unique. N'importe quel nœud accepte n'importe quelle clé, de sorte que le balancer peut vous envoyer n'importe où et qu'un nœud mort sort simplement de la rotation. C'est le modèle le plus simple et celui vers lequel nous nous tournerions en premier.

Sentinel. Pour les clients construits autour de Redis Sentinel, Zaris répond aux commandes de découverte Sentinel afin que le client puisse trouver un nœud et le résoudre à nouveau après une défaillance, en utilisant le même chemin de code qu'il possède déjà.

Cluster. Pour les clients qui parlent Redis Cluster, Zaris présente une topologie de cluster avec un hachage aligné sur CRC16 — le même calcul de slot qu'utilisent les clients Redis Cluster — de sorte que l'idée que se fait un client du slot auquel appartient une clé corresponde au propre routage de Zaris. (Cet alignement explique en partie pourquoi la 2.0.0 est une version majeure ; voir l'annonce de la 2.0.0.)

Ce qui est pris en charge, et ce qui est proprement exclu​

Nous avons passé toute la surface de StackExchange.Redis face à un nœud RESP de Zaris et classé chaque appel. Résultat : ~126 commandes prises en charge, ~92 proprement non prises en charge, et zéro échec. « Proprement non prise en charge » est l'expression importante — une commande non prise en charge renvoie une erreur en bonne et due forme, elle ne fait pas planter la connexion ni ne corrompt l'état.

Le périmètre pris en charge couvre le cœur du quotidien : les chaînes et GET/SET/DEL, EXPIRE/TTL, MGET/MSET, INCR/DECR, les hashes, les listes, les sets, les sorted sets, les Streams (XADD/XREAD/XREADGROUP avec groupes de consommateurs et lectures bloquantes), MULTI/EXEC/WATCH, SUBSCRIBE/PUBLISH, et les notifications d'espace de clés.

Sont proprement rejetées les fonctionnalités qui présupposent un moteur différent en dessous : le scripting Lua (EVAL), les commandes geo, HyperLogLog, les bitmaps, les opérations *STORE inter-clés, les retraits bloquants de liste, les commandes de persistance RDB/AOF, l'administration CLUSTER, et l'introspection PUBSUB. Considérez Zaris comme un store KV + collections + streams rapide et hautement disponible qui parle Redis — et non comme un remplacement direct de chaque module Redis. La grille complète figure dans la matrice de compatibilité des commandes Redis.

Figez les versions de votre client​

Le listener est en RESP2. Parce que le point de terminaison est RESP2 uniquement et qu'il s'agit d'une surface de compatibilité plutôt que d'une réimplémentation complète de Redis, figez les versions de votre bibliothèque client Redis plutôt que de les laisser flotter — un client qui négocie silencieusement RESP3 ou recourt à une commande non prise en charge devrait échouer de façon prévisible, et le figement maintient ce comportement stable d'un déploiement à l'autre.

Se connecter​

Une fois qu'un nœud a son listener RESP activé, c'est juste un point de terminaison Redis :

redis-cli -h node-a.internal -p 6379 SET user:42 "ada"
redis-cli -h node-a.internal -p 6379 GET user:42
import redis
r = redis.Redis(host="node-a.internal", port=6379)
r.set("user:42", "ada")
print(r.get("user:42"))
// StackExchange.Redis, unchanged
var mux = ConnectionMultiplexer.Connect("node-a.internal:6379");
var db = mux.GetDatabase();
db.StringSet("user:42", "ada");

Où aller ensuite​

Le front-end RESP est la façon dont une équipe polyglotte adopte Zaris sans réécriture ; le client .NET natif est la façon dont un service .NET en tire le meilleur parti. Si vous migrez une application Redis existante, Vous venez de Redis parcourt le chemin pratique, et si le débit en lecture vous importe, Des lectures qui montent en charge avec vos cœurs montre comment le routage direct-vers-le-propriétaire permet aux lectures de monter en charge avec le cluster.