En una serie de varios artículos, presentaré mi traducción adaptada de la sección
Redis Best Practices del sitio web oficial de Redis Labs.
Redis se puede usar de innumerables maneras, sin embargo, hay varios patrones que se pueden usar para resolver problemas comunes. Hemos compilado una colección de patrones comunes que consideramos las
mejores prácticas para resolver estos problemas. Esta colección no es exhaustiva y no parece ser un conjunto de las únicas formas de usar Redis, pero esperamos que sirva como un punto de partida para resolver problemas usando Redis.
Dividimos esta guía de mejores prácticas en capítulos y subcapítulos según sea necesario (Nota del traductor: algunos subcapítulos son cortos, por lo que los combinaré en uno):
- en el capítulo "Patrones de indexación", veremos formas de ir más allá del acceso habitual de valores clave con Redis. Incluye formas de usar patrones clave de manera inteligente utilizando varios tipos de datos Redis para ayudar no solo a encontrar datos, sino también a reducir la complejidad del acceso;
- El capítulo Patrones de interacción se centra en los patrones de Redis que mueven los datos a través de la infraestructura. En este caso, Redis no actúa como un repositorio, sino como una guía de datos;
- El capítulo “Patrones de almacenamiento de datos” describe métodos para guardar representaciones complejas de datos en Redis. Calcularemos scripts complejos de documentos que se pueden generalizar de formas simples y complejas;
- los patrones con respecto a los datos almacenados temporalmente se describen en el capítulo "Patrones de series temporales";
- El límite de velocidad se usa a menudo en Redis. En el capítulo "Patrones básicos de limitación de velocidad" pasaremos a los conceptos básicos de sus casos de uso;
- El filtro Bloom se ha visto durante mucho tiempo en Redis, y en el capítulo "Patrones con un filtro Bloom" observamos las estructuras de datos probabilísticas y cómo difieren de sus análogos improbables;
- El mostrador es una recepción sorprendentemente profunda. En un capítulo separado, exploramos cómo calcular la actividad y los elementos únicos de manera computacional eficiente;
- finalmente, hablaremos sobre cómo aprovechar Lua para que Redis haga más con menos.
Esta guía es inconsistente, por lo que puede comenzar con cualquier capítulo. También puede usar la navegación al comienzo de cada publicación para encontrar algo adecuado.
Patrones de indexación
Conceptualmente, Redis es una base de datos basada en el paradigma clave / valor, cuando cada pieza de datos está asociada a una determinada clave. Si desea obtener datos para algo que no sea una clave, deberá implementar un índice que utilice uno de los muchos tipos de datos disponibles en Redis.
La indexación en Redis es bastante diferente de la que se presenta en otras bases de datos, por lo que sus propios escenarios de uso y datos determinarán la mejor estrategia para la indexación. En este capítulo, veremos algunas estrategias generales de recuperación de datos además de la recuperación simple de clave / valor:
- conjuntos ordenados como índices;
- índices lexicográficos;
- índices geoespaciales;
- Geolocalización de IP
- búsqueda de texto completo;
- índices particionados.
Conjuntos ordenados como índices
Los conjuntos ordenados (ZSET) son un tipo de datos estándar en Redis que representa muchos objetos únicos (las repeticiones no se guardan), donde cada objeto se asigna a un número (llamado "conteo"), que actúa como un mecanismo de clasificación natural. Y aunque los objetos no pueden repetirse, algunos objetos pueden tener el mismo recuento. Con una complejidad de tiempo relativamente baja para agregar, eliminar y obtener un rango de valores (por rango o recuento), los conjuntos ordenados son bastante adecuados para ser índices. Como ejemplo, tome los países del mundo, clasificados por población:
> ZADD countries-by-pop 1409517397 china > ZADD countries-by-pop 146573899 russia > ZADD countries-by-pop 81456724 germany > ZADD countries-by-pop 333016381 usa > ZADD countries-by-pop 1 mars > ZADD countries-by-pop 37290812 afghanistan > ZADD countries-by-pop 1388350202 india
Obtener los 5 principales países será simple:
> ZRANGE countries-by-pop 0 4 1) "mars" 2) "afghanistan" 3) "germany" 4) "russia" 5) "india"
Y obtener países con poblaciones entre 10,000,000 y 1,000,000,000:
> ZRANGEBYSCORE countries-by-pop 10000000 1000000000 1) "afghanistan" 2) "germany" 3) "russia"
Puede crear múltiples índices para demostrar diferentes formas de ordenar los datos. En nuestro ejemplo, podríamos usar los mismos objetos, pero en lugar de la cantidad de personas, tomemos la densidad de población, el tamaño geográfico, la cantidad de usuarios de Internet, etc. Esto creará índices de alto rendimiento para varios aspectos. Además, al dividir el nombre de un objeto con datos sobre él almacenados en Redis (en Hash, por ejemplo) o en otro almacén de datos, el proceso secundario podría obtener información adicional sobre cada elemento según sea necesario.
Índices lexicográficos
Los conjuntos ordenados (ZSET) con clasificación por conteo tienen una propiedad interesante que se puede usar para crear un mecanismo para la clasificación alfabética aproximada. La propiedad es que los objetos con la misma puntuación se pueden devolver en orden lexicográfico y por valores límite. Tome los siguientes datos:
> ZADD animal-list 0 bison 0 boa 0 dog 0 emu 0 falcon 0 alligator 0 chipmunk
Este comando agregará varios animales a la tecla de lista de animales. Cada objeto tiene una puntuación de 0. Después de ejecutar el comando ZRANGE con los argumentos 0 y -1, vemos un curioso orden:
> ZRANGE animal-list 0 -1 1) "alligator" 2) "bison" 3) "boa" 4) "chipmunk" 5) "dog" 6) "emu" 7) "falcon"
Aunque los elementos no se agregaron alfabéticamente, se devolvieron ordenados alfabéticamente. Este orden es el resultado de la comparación de cadenas binarias, byte por bit. Esto significa que los caracteres ASCII se devolverán en orden alfabético. Esto sugiere lo siguiente:
- los caracteres en minúscula y mayúscula no se reconocerán como lo mismo;
- Los caracteres multibyte no se ordenarán como se esperaba.
Redis también proporciona algunas características avanzadas para reducir aún más las búsquedas lexicográficas. Por ejemplo, queremos devolver animales comenzando con
b y terminando con
e . Podemos usar el siguiente comando:
> ZRANGEBYLEX animal-list [b (f 1) "bison" 2) "boa" 3) "chipmunk" 4) "dog" 5) "emu"
El argumento
(f puede ser un poco confuso. Este es un punto importante, porque Redis no tiene idea sobre la comprensión literal de las letras del alfabeto. Esto significa que debemos tener en cuenta que todo lo que comienza con
e siempre estará antes que todo lo que comience con
f , independientemente de las letras posteriores Otra nota es que el corchete indica búsqueda con inclusión, y el corchete indica búsqueda sin inclusión. En nuestro caso, si consultamos con
b , esto se incluirá en la lista, mientras que
f no aparecerá en la selección. Si necesita todos los elementos hasta el final, use codificado el último símbolo (255 o 0xFF):
> ZRANGEBYLEX animal-list [c "[\xff" 1) "chipmunk" 2) "dog" 3) "emu" 4) "falcon"
Este comando también puede ser limitado, asegurando así la paginación:
> ZRANGEBYLEX animal-list [b (f LIMIT 0 2 1) "bison" 2) "boa" > ZRANGEBYLEX animal-list [b (f LIMIT 2 2 1) "chipmunk" 2) "dog"
El único inconveniente es que la complejidad del tiempo aumentará a medida que aumente la sangría (primer argumento después de LIMIT). Por lo tanto, si tiene 1 millón de objetos y está tratando de obtener los dos últimos, esto requerirá un rastreo de solo un millón.
Índices geoespaciales
Redis tiene varios equipos de indexación geoespacial (equipos GEO), pero a diferencia de otros equipos, estos equipos no tienen sus propios tipos de datos. Estos comandos en realidad complementan el tipo de conjunto ordenado. Esto se logra codificando la latitud y la longitud en la puntuación del conjunto ordenado utilizando el algoritmo geohash.
Agregar elementos a un índice geográfico es fácil. Suponga que está siguiendo a un grupo de automóviles que circulan por una carretera. Llamamos a este conjunto de autos simplemente "autos". Digamos que su máquina en particular se puede identificar como un objeto "my-car" (utilizamos el término "objeto" porque el índice geográfico es simplemente una forma del conjunto). Para agregar una máquina al conjunto, podemos ejecutar el comando:
> GEOADD cars -115.17087 36.12306 my-car
El primer argumento es el conjunto al que agregamos, el segundo es la latitud, el tercero es la longitud y el cuarto es el nombre del objeto.
Para actualizar la ubicación de la máquina, solo necesita ejecutar el comando nuevamente con las nuevas coordenadas. Esto funciona porque un índice geográfico es simplemente un conjunto donde no se permiten elementos duplicados.
> GEOADD cars -115.17172 36.12196 my-car
Agregue un segundo automóvil a "automóviles". Esta vez Volodia la lleva:
> GEOADD cars -115.171971 36.120609 volodias-car
Mirando las coordenadas, puedes decir que estos autos están bastante cerca uno del otro, pero ¿cuánto? Puede definir esto con el comando GEODIST:
> GEODIST cars my-car volodias-car "151.9653"
Esto significa que dos vehículos están separados por aproximadamente 151 metros. También puedes calcular en otras unidades:
> GEODIST cars my-car robins-car ft "498.5737"
Esto devolvió la misma distancia en pasos. También puede usar millas (ml) o kilómetros (km).
Ahora veamos quién está en el radio de cierto punto:
> GEORADIUS cars -115.17258 36.11996 100 m 1) "volodias-car"
Esto devolvió a todos dentro de un radio de 100 metros alrededor del punto especificado. También puede solicitar a todos en el radio de cualquier objeto del conjunto:
> GEORADIUSBYMEMBER cars volodias-car 152 m 1) "volodias-car" 2) "my-car"
También podemos habilitar la distancia agregando el argumento WITHDIST opcional (esto funciona para GEORADIUS o GEORADIUSBYMEMBER):
> GEORADIUSBYMEMBER cars volodias-car 152 m WITHDIST 1) 1) "volodias-car" 2) "0.0000" 2) 1) "my-car" 2) "151.9653"
Otro argumento opcional para GEORADIUS y GEORADIUSBYMEMBER es WITHCOORD, que devuelve las coordenadas de cada objeto. WITHDIST y WITHCOORD pueden usarse juntas o por separado:
> GEORADIUSBYMEMBER cars volodias-car 152 m WITHDIST WITHCOORD 1) 1) "volodias-car" 2) "0.0000" 3) 1) "-115.17197102308273315" 2) "36.12060917648089031" 2) 1) "my-car" 2) "151.9653" 3) 1) "-115.17171889543533325" 2) "36.12196018285882104"
Dado que los índices geoespaciales son solo una alternativa a los conjuntos ordenados, se pueden utilizar algunos operadores de estos últimos. Si queremos eliminar "my-car" del conjunto de "carros", podemos usar el comando del conjunto ordenado de ZREM:
> ZREM cars my-car
Redis proporciona un rico conjunto de herramientas para trabajar con geoespacial, y en esta sección examinamos solo las básicas.
Geolocalización de IP
Encontrar la ubicación real del servicio conectado puede ser muy útil. Las tablas de geolocalización de IP suelen ser bastante grandes y difíciles de administrar de manera efectiva. Podemos usar conjuntos ordenados para implementar servicios de geolocalización IP rápidos y eficientes.
IPv4 se hace referencia con mayor frecuencia en notación decimal (74.125.43.99, por ejemplo). Sin embargo, los servicios de red ven esta misma dirección como un número de 32 bits, y cada byte representa uno de los cuatro números en forma decimal. El ejemplo anterior sería 0x4A7D2B63 en hexadecimal o 1249717091 en decimal.
Los conjuntos de datos de geolocalización IP están ampliamente disponibles y generalmente toman la forma de una tabla simple con tres columnas (inicio, final, ubicación). El principio y el final son la representación decimal de IPv4. En Redis, podemos adaptar conjuntos ordenados a este formato, porque no hay "agujeros" en los rangos de IP, por lo tanto, podemos asumir con seguridad que el final de un rango es el comienzo de otro.
Para un ejemplo simple, agregue algunos rangos a los conjuntos ordenados:
> ZADD ip-loc 1249716479 us:1 > ZADD ip-loc 1249716735 taiwan:1 > ZADD ip-loc 1249717759 us:2 > ZADD ip-loc 1249718015 finland:1
El primer argumento es la clave de nuestro conjunto, el segundo es la representación decimal del final del rango de IP y el último es el objeto en sí. Tenga en cuenta que el objeto establecido tiene un número después de los dos puntos. Esto es solo para facilitar un ejemplo. Las tablas de IP reales tienen identificadores únicos para cada rango (y más información adicional que solo el nombre del país).
Para consultar la tabla para una dirección IP dada, podemos usar el comando ZRANGEBYSCORE con algunos argumentos adicionales. Tome la dirección IP y conviértala a decimal. Esto se puede hacer usando su lenguaje de programación. Primero, use la dirección del ejemplo original 74.125.43.99 (1249717091). Si tomamos este número como punto de referencia y no especificamos un máximo, y luego limitamos el resultado solo al primer objeto, encontraremos su geolocalización:
> ZRANGEBYSCORE ip-loc 1249717091 +inf LIMIT 0 1 1) "us:2"
El primer argumento es la clave de nuestro conjunto ordenado, el segundo es la representación decimal de la dirección IP, el tercero (+ inf) le dice a Redis que solicite sin un límite superior, y los últimos tres argumentos simplemente indican que queremos obtener solo el primer resultado.
Búsqueda de texto completo
Antes de la llegada de los módulos, la búsqueda de texto completo se implementó utilizando los comandos nativos de Redis. El módulo RedisSearch es mucho más productivo que este patrón, sin embargo, en algunos entornos no está disponible. Además, este patrón es muy interesante y puede generalizarse a otras cargas de trabajo en las que RedisSearch no será ideal.
Supongamos que tenemos varios documentos de texto que deben buscarse. Este puede no ser el caso de uso obvio para Redis, ya que proporciona acceso clave, pero, por otro lado, Redis se puede utilizar como un motor de búsqueda de texto completo completamente nuevo.
Primero, tomemos algunos textos de muestra en los documentos:
"Redis es muy rápido""Los guepardos son rápidos""Los guepardos tienen manchas"Los dividimos en conjuntos de palabras, separados por un espacio para la simplicidad:
> SADD ex1 redis is very fast > SADD ex2 cheetahs are very fast > SADD ex3 cheetahs have spots
Observe que ponemos cada línea en su propio conjunto. Puede parecer que solo estamos agregando la línea completa: SADD es variable y toma varios elementos a la vez como argumentos. También convertimos todas las palabras a minúsculas.
Entonces necesitamos invertir este índice y mostrar qué palabra está contenida en qué documento. Para hacer esto, haremos un conjunto para cada palabra y pondremos el nombre del documento como un objeto:
> SADD redis ex1 > SADD is ex1 > SADD very ex1 ex2 > SADD fast ex1 ex2 > SADD cheetahs ex2 ex3 > SADD have ex3 > SADD spots ex3
Para mayor claridad, dividimos esto en diferentes comandos, pero todos los comandos generalmente se ejecutan atómicamente en el bloque MULTI / EXEC.
Para consultar nuestro pequeño índice de texto completo, utilizamos el comando SINTER (intersección de conjuntos). Encuentre documentos con "muy" y "rápido":
> SINTER very fast 1) "ex2" 2) "ex1"
En el caso de que no haya documentos que coincidan con la solicitud, obtendremos un resultado vacío:
> SINTER cheetahs redis (empty list or set)
Por consistencia, es mejor usar SUNION en lugar de SINTER:
> SUNION cheetahs redis 1) "ex2" 2) "ex1" 3) "ex3"
Eliminar un objeto del índice es un poco más complicado. Primero, obtenga palabras indexadas del documento, luego elimine el identificador del documento de cada palabra:
> SMEMBERS ex3 1) "spots" 2) "have" 3) "cheetahs" > SREM have ex3 > SREM cheetahs ex3 > SREM spots ex3
Redis no tiene un operador separado para realizar todos estos pasos con un solo comando, por lo que primero debe consultar con el comando SMEMBERS, luego eliminar cada objeto secuencialmente usando SREM.
Por supuesto, esta es una búsqueda de texto completo muy simplificada. Puede hacerlo más avanzado utilizando conjuntos ordenados en lugar de los habituales. En este caso, si una palabra aparece más de una vez en un documento, puede clasificarla más arriba que el documento en el que aparece una vez. Los patrones descritos anteriormente son más o menos los mismos, con la excepción de los tipos de conjuntos utilizados.
Índices particionados
Una sola instancia (o fragmento) de Redis es muy viable, pero hay circunstancias en las que podría necesitar un índice distribuido en varias instancias. Por ejemplo, para aumentar el rendimiento al paralelizar índices que son más grandes que el espacio libre de una instancia. Supongamos que desea realizar una operación en varias teclas. Una forma efectiva de separar (particionar) estas claves es garantizar una distribución uniforme de las claves en cada partición, realizar cualquier operación en cada partición en paralelo y luego combinar los resultados al final.
Para lograr una distribución uniforme de claves, utilizaremos un algoritmo de cifrado no criptográfico. Cualquier función rápida de hash servirá, pero usamos el famoso CRC-32 como ejemplo. En la mayoría de los casos, estos algoritmos devuelven el resultado en hexadecimal (para "my-cool-document" CRC-32 devolverá F9FDB2C9). La representación hexadecimal es más simple para la máquina, pero es solo otra representación de enteros decimales, lo que significa que puede realizar cálculos sobre estos valores.
A continuación, debe determinar el número de particiones; esto debería ser al menos x2 del número de copias. Esto contribuye aún más a la escala.
Digamos que tenemos 3 copias y 6 particiones. La partición a la que se envía el documento se puede calcular mediante la siguiente operación:
CRC32(“my-cool-document”) = F9FDB2C9 (16) 4194153161 (10) 4194153161 mod 6 = 5
En Redis Enterprise, puede controlar a qué partición pertenece la clave, utilizando expresiones regulares predefinidas o envolviendo parte de la clave con llaves. Entonces, para nuestro ejemplo, podemos establecer la clave para el documento de la siguiente manera:
idx: my-cool-document {5}Luego tenemos otro documento que emite una partición con el número 3, por lo tanto, la clave se verá así:
idx: mi-otro-documento {3}Si tiene teclas auxiliares adicionales con las que necesitará trabajar y que están asociadas con este documento, debe estar en la misma partición para poder realizar operaciones con ambas teclas al mismo tiempo sin encontrar un montón de errores. Para hacer esto, debe agregar el mismo número de partición a la clave que el documento.
Si observa sus datos de forma remota, verá que su índice se distribuye de manera bastante uniforme entre las particiones. Puede paralelizar la tarea que debe realizarse para cada partición. Cuando tiene una tarea que debe realizarse a través de todo el índice, su aplicación deberá realizar la misma lógica para cada partición, devolver el resultado y combinar según sea necesario en la aplicación.
Sobre esto, el primer artículo llega a su fin. La siguiente será una traducción de los subtítulos "Patrones de interacción" y "Patrones de almacenamiento de datos".