Saltar al contenido principal

Cualquier lenguaje, un solo almacén: cómo Zaris habla el protocolo de Redis

· 8 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

Cualquier lenguaje, un solo almacén

Zaris es un almacén clave/valor distribuido construido de forma nativa para .NET. Sus clientes de primera clase hablan un protocolo binario eficiente y, si tu stack es .NET, esa es la ruta que quieres: APIs asíncronas reales, tipos de .NET, primitivas de coordinación, reemplazos directos de IDistributedCache y HybridCache.

Pero la mayoría de los sistemas no son solo .NET. Hay un servicio en Python haciendo trabajo de datos, un sidecar en Go, un ejecutor de trabajos en Ruby, una pasarela en Node. Reescribir todos ellos sobre un cliente nuevo para probar un almacén nuevo es inviable. Así que, en la 2.0.0, cada nodo de Zaris también puede exponer un listener del protocolo de Redis (RESP2): apunta un cliente de Redis existente a él y usa Zaris como almacén clave/valor de reemplazo directo, en cualquier lenguaje, sin cambiar una sola línea de código del cliente.

Este artículo trata de cómo funciona realmente ese front-end: el modelo de enrutamiento, el modelo de datos, los modelos de conmutación por error y exactamente qué comandos entran y salen.

Por qué poner una cara de Redis a un almacén de .NET​

La razón honesta es el alcance. Redis tiene un cliente en todos los lenguajes, y toda una generación de desarrolladores ya conoce su vocabulario de comandos. Si Zaris puede aceptar esos comandos, un equipo políglota obtiene un único almacén replicado y de alta disponibilidad con el que todos los servicios pueden hablar hoy, mientras que los servicios de .NET todavía pueden bajar al cliente nativo para APIs tipadas, transacciones, concesiones, contadores y lo demás.

El front-end RESP está desactivado por defecto y se habilita por nodo, porque es una superficie de compatibilidad opcional, no el protocolo principal. Cuando está activado, un nodo escucha en un puerto de Redis y responde a comandos de Redis ordinarios.

El hash central está alineado con los slots de Redis​

Esta es la parte que hace que funcione a través de un clúster sin un proxy en medio. Zaris aplica el hash a las claves exactamente de la forma en que lo hace Redis Cluster: CRC16(key) % 16384 da un slot, y los slots se asignan a las propias particiones de Zaris. Como el enrutamiento interno y el cálculo de slots de Redis son el mismo, cada nodo puede anunciar una vista precisa de CLUSTER SLOTS / CLUSTER NODES: para cada rango de slots, el nodo que es el propietario principal de la partición de ese slot, que es exactamente donde vive la clave.

Así que un cliente de Redis consciente del clúster enruta cada clave directamente a su propietario:

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

No hay proxy ni salto adicional. El cliente aplica el hash a la clave, busca al propietario en el mapa de slots que obtuvo en el momento de la conexión y se conecta directamente a ese nodo, lo mismo que ya hace contra Redis Cluster real. Cuando el comando aterriza en el nodo propietario de la clave, Zaris lo sirve desde el almacén local en proceso, saltándose incluso un salto de loopback. En una conmutación por error o reequilibrio, el nodo responde con un MOVED, el cliente actualiza su mapa de slots y continúa. Es el comportamiento ordinario de Redis Cluster, respaldado por el motor particionado de Zaris.

Si prefieres no ejecutar un cliente consciente del clúster, no tienes que hacerlo: pon los nodos detrás de un balanceador de carga o usa Sentinel en su lugar, y cualquier nodo aceptará cualquier clave. Esos modelos están más abajo.

El modelo de valor completo para las colecciones​

Zaris 2.0.0 tiene hashes, listas, conjuntos y conjuntos ordenados nativos. Sobre RESP, los comandos de colección (HSET, LPUSH, SADD, ZADD y afines) se asignan a esas estructuras nativas. Bajo el capó, una mutación es una lectura-modificación-escritura sobre un valor completo replicado: el nodo lee la colección actual, aplica tu cambio y la vuelve a escribir con un compare-and-swap para que los escritores concurrentes no puedan pisarse entre sí. Eso mantiene la semántica correcta a través de la replicación sin necesidad de operaciones de cable por campo, y es el mismo mecanismo que usan las APIs nativas de estructuras de datos.

Transacciones: MULTI/EXEC sobre 2PC real​

MULTI/EXEC no se simula. Una transacción en cola se ejecuta sobre la maquinaria nativa de transacciones de confirmación en dos fases de Zaris, de modo que los comandos del bloque se confirman juntos. Los comandos de contador también se comportan correctamente dentro de una transacción: INCR, DECR, INCRBY y DECRBY leen, calculan y escriben dentro de la transacción en lugar de competir fuera de ella. El patrón de bloqueo distribuido SET key val NX PX ttl también funciona, lo que cubre el uso más común al estilo Redlock.

Tres formas de conmutar por error​

Como un clúster de Zaris son varios nodos, un cliente de Redis necesita una estrategia para saber qué hacer cuando el nodo al que está conectado desaparece. Zaris admite los tres modelos de conmutación por error que los clientes de Redis ya entienden, de modo que eliges el que tu biblioteca cliente e infraestructura ya asumen:

Balanceador de carga (opción por defecto recomendada). Pon los nodos detrás de un balanceador de carga TCP y conéctate al único punto de acceso virtual. Cualquier nodo acepta cualquier clave, así que el balanceador puede enviarte a cualquier parte y un nodo caído simplemente sale de la rotación. Este es el modelo más simple y el que elegiríamos primero.

Sentinel. Para clientes construidos en torno a Redis Sentinel, Zaris responde a los comandos de descubrimiento de Sentinel para que el cliente pueda encontrar un nodo y volver a resolver tras un fallo, usando la misma ruta de código que ya tiene.

Clúster. Para clientes que hablan Redis Cluster, Zaris presenta la topología del clúster con hashing alineado con CRC16 —el mismo cálculo de slots que usan los clientes de Redis Cluster— de modo que la idea del cliente sobre a qué slot pertenece una clave coincide con el propio enrutamiento de Zaris. (Esta alineación es parte del porqué la 2.0.0 es una versión mayor; consulta el anuncio de la 2.0.0.)

Qué entra y qué queda limpiamente fuera​

Ejecutamos toda la superficie de StackExchange.Redis contra un nodo RESP de Zaris y clasificamos cada llamada. El resultado: ~126 comandos soportados, ~92 no soportados limpiamente y cero fallos. "No soportado limpiamente" es la frase importante: un comando no soportado devuelve un error adecuado, no bloquea la conexión ni corrompe el estado.

Lo soportado cubre el núcleo del día a día: cadenas y GET/SET/DEL, EXPIRE/TTL, MGET/MSET, INCR/DECR, hashes, listas, conjuntos, conjuntos ordenados, Streams (XADD/XREAD/XREADGROUP con grupos de consumidores y lecturas bloqueantes), MULTI/EXEC/WATCH, SUBSCRIBE/PUBLISH y las notificaciones de espacio de claves.

Lo rechazado limpiamente son las funciones que asumen un motor diferente debajo: scripting Lua (EVAL), comandos geo, HyperLogLog, bitmaps, operaciones *STORE entre claves, pops de listas bloqueantes, comandos de persistencia RDB/AOF, administración de CLUSTER e introspección de PUBSUB. Trata a Zaris como un almacén rápido y de alta disponibilidad de KV + colecciones + streams que habla Redis, no como un reemplazo directo para cada módulo de Redis. La cuadrícula completa está en la matriz de compatibilidad de comandos de Redis.

Fija las versiones de tu cliente​

El listener es RESP2. Como el punto de acceso es solo RESP2 y esta es una superficie de compatibilidad en lugar de una reimplementación completa de Redis, fija las versiones de tu biblioteca cliente de Redis en lugar de dejarlas flotar: un cliente que negocie silenciosamente RESP3 o eche mano de un comando no soportado debería fallar de forma predecible, y fijar las versiones mantiene ese comportamiento estable entre despliegues.

Conexión​

Una vez que un nodo tiene su listener RESP habilitado, es simplemente un punto de acceso de 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");

Adónde ir a continuación​

El front-end RESP es la forma en que un equipo políglota adopta Zaris sin una reescritura; el cliente nativo de .NET es la forma en que un servicio de .NET le saca el máximo partido. Si estás migrando una aplicación de Redis existente, Si vienes de Redis recorre la ruta práctica, y si te importa el rendimiento de lectura, Lecturas que escalan con tus núcleos muestra cómo el enrutamiento directo al propietario permite que las lecturas escalen con el clúster.