¿Por qué Redis usa dos estructuras de datos para el almacenamiento de datos al mismo tiempo?

prefacio

En Redis, hay un tipo de datos. Al almacenar, se utilizan dos estructuras de datos para almacenarlos por separado. Entonces, ¿por qué Redis hace esto? ¿Hacer esto hará que los mismos datos ocupen el doble de espacio?

Cinco tipos básicos de objetos de colección

Un objeto de colección en Redis es una colección desordenada que contiene elementos de tipo cadena, y los elementos de la colección son únicos y no repetibles.

Hay dos estructuras de datos subyacentes para los objetos de colección: intset y hashtable. Internamente, la codificación se utiliza para distinguir:

codificación de inserción

intset (conjunto de enteros) puede contener valores enteros de tipo int16_t, int32_t, int64_t, y se garantiza que no hay elementos duplicados en el conjunto.

La estructura de datos intset se define de la siguiente manera (en el código fuente inset.h):

typedef struct intset {
    uint32_t encoding;//编码方式
    uint32_t length;//当前集合中的元素数量
    int8_t contents[];//集合中具体的元素
} intset;

复制代码

La siguiente figura es un diagrama simplificado del almacenamiento de objetos de colección de un intset:

codificación

La codificación dentro de intset registra el tipo de almacenamiento de datos del conjunto entero actual, hay tres tipos principales:

  • INTSET_ENC_INT16

En este momento, cada elemento en contenidos[] es un valor entero de tipo int16_t, el rango es: -32768 ~ 32767 (-2 a la potencia 15 ~ 2 a la potencia 15 - 1).

  • INTSET_ENC_INT32

En este momento, cada elemento en contenidos[] es un valor entero de tipo int32_t, el rango es: -2147483648 ~ 2147483647 (-2 elevado a la 31ª potencia ~ 2 elevado a la 31ª potencia - 1).

  • INTSET_ENC_INT64

En este momento, cada elemento de contenidos[] es un valor entero de tipo int64_t, y el rango es: -9223372036854775808 ~ 9223372036854775807 (-2 elevado a la 63ª potencia ~ 2 elevado a la 63ª potencia - 1).

contenido[]

contenidos[] Aunque la definición de la estructura está escrita como tipo int8_t, el tipo de almacenamiento real está determinado por la codificación anterior.

Actualización de colección de enteros

Si los elementos en el conjunto de enteros son todos de 16 bits al principio y se almacenan en el tipo int16_t, entonces se debe almacenar otro entero de 32 bits, luego se debe actualizar el conjunto de enteros original y el entero de 32 bits se puede almacenar después de la actualización Almacenado en una colección de enteros. Esto implica la actualización de tipo del conjunto de enteros. El proceso de actualización consta principalmente de 4 pasos:

  • Expanda el tamaño del espacio de matriz subyacente de acuerdo con el tipo de elemento recién agregado y asigne el nuevo espacio de acuerdo con la cantidad de bits de los elementos existentes después de la actualización.
  • Convierta los elementos existentes y vuelva a colocar los elementos convertidos en la matriz uno por uno de atrás hacia adelante.
  • Coloque el nuevo elemento al principio o al final de la matriz (porque la condición para activar la actualización es que el tipo de número entero de la matriz actual no puede almacenar el nuevo elemento, por lo que el nuevo elemento es más grande o más pequeño que el elemento existente).
  • Modifique la propiedad de codificación a la codificación más reciente y modifique la propiedad de longitud de forma síncrona.

PD: Al igual que la codificación de objetos de cadena, una vez que se actualiza el tipo de la colección de enteros, la codificación permanecerá y no se podrá degradar.

Ejemplo de actualización

1. Supongamos que tenemos un almacenamiento de colección cuya codificación es int16_t y almacena internamente 3 elementos:

2. En este momento, se necesita insertar un número entero 50000 y se encuentra que no se puede almacenar, y 50000 es un número entero de tipo int32_t, por lo que es necesario solicitar un nuevo espacio y el tamaño de la aplicación. el espacio es 4 * 32 - 48=80.

3. Ahora hay 4 elementos para colocar en la nueva matriz, y la matriz original ocupa el tercer lugar, por lo que es necesario mover los 3 actualizados a los bits 64-95.

4. Continúe y mueva los 2 actualizados a 32-63 bits.

5. Continúe moviendo el 1 actualizado a los bits 0-31.

6. El 50000 se colocará en los bits 96-127.

7. Finalmente, se modificarán los atributos de codificación y longitud, y la actualización se completará después de la modificación.

codificación de tabla hash

La estructura de la tabla hash ha sido analizada en detalle en la descripción anterior del objeto hash, si quieres saber más al respecto puedes hacer clic aquí.

conversión de codificación intset y hashtable

Redis elegirá usar la codificación intset cuando una colección cumpla con las siguientes dos condiciones:

  • Todos los elementos que contiene un objeto de colección son valores enteros.
  • El número de elementos almacenados en el objeto de colección es menor o igual a 512 (este umbral puede controlarse mediante el archivo de configuración set-max-intset-entries).

Una vez que los elementos de la colección no cumplan con las dos condiciones anteriores, se seleccionará la codificación de tabla hash.

Comandos comunes para objetos de colección

  • sadd key miembro1 miembro2: agregue uno o más elementos miembro a la clave establecida y devuelva el número de adiciones exitosas.Si el elemento ya existe, se ignorará.
  • miembro clave sismember: determine si el miembro del elemento existe en la clave establecida.
  • srem clave miembro1 miembro2: elimine los elementos de la clave establecida y los elementos no existentes se ignorarán.
  • smove source dest member: Mueva el miembro del elemento desde el origen de la colección al dest, o no haga nada si el miembro no existe.
  • smembers key: Devuelve todos los elementos de la clave set.

Conociendo los comandos comunes para manipular objetos de colección, podemos verificar el tipo y la codificación del objeto hash mencionado anteriormente.Antes de probar, para evitar la interferencia de otros valores clave, primero ejecutamos el comando flushall para borrar la base de datos de Redis.

Ejecute los siguientes comandos en secuencia:

sadd num 1 2 3  //设置 3 个整数的集合,会使用 intset 编码
type num //查看类型
object encoding num   //查看编码

sadd name 1 2 3 test  //设置 3 个整数和 1 个字符串的集合,会使用 hashtable 编码
type name //查看类型
object encoding name //查看编码 

复制代码

Consigue el siguiente efecto:

Se puede ver que cuando solo hay números enteros en los elementos del conjunto, el conjunto usa la codificación intset, y cuando los elementos del conjunto contienen números que no son enteros, se usa la codificación hashtable.

Cinco tipos básicos de objetos de colección ordenados

La diferencia entre un conjunto ordenado y un conjunto en Redis es que cada elemento del conjunto ordenado se asocia con una puntuación de tipo doble y luego se clasifica en orden ascendente de la puntuación. En otras palabras, el orden del conjunto ordenado está determinado por la fracción cuando establecemos el valor nosotros mismos.

Hay dos estructuras de datos subyacentes para objetos de conjuntos ordenados: skiplist y ziplist. Internamente, también se distingue por codificar:

codificación de lista de saltos

skiplist es la lista de saltos, a veces denominada simplemente lista de saltos. El objeto de conjunto ordenado codificado con skiplist utiliza la estructura zset como implementación subyacente, y el zset contiene tanto un diccionario como una lista de omisión.

mesa de salto

La tabla de salto es una estructura de datos ordenada, y su característica principal es lograr el propósito de acceder a los nodos rápidamente manteniendo múltiples punteros a otros nodos en cada nodo.

En la mayoría de los casos, la eficiencia de la tabla de saltos puede ser igual a la del árbol balanceado, pero la implementación de la tabla de saltos es mucho más simple que la implementación del árbol balanceado, por lo que Redis elige usar la tabla de saltos para implementar el orden. colocar.

La siguiente figura es una lista enlazada ordenada ordinaria. Si queremos encontrar el elemento 35, solo podemos recorrer desde el principio hasta el final (los elementos en la lista enlazada no admiten el acceso aleatorio, por lo que no se puede usar la búsqueda binaria, y se puede acceder a la matriz aleatoriamente a través de subíndices, por lo que la búsqueda binaria generalmente se aplica a matrices ordenadas), y la complejidad del tiempo es O (n).

Entonces, si podemos saltar directamente al medio de la lista enlazada, podemos ahorrar muchos recursos. Este es el principio de la lista de salto. Como se muestra en la siguiente figura, es un ejemplo de la estructura de datos de la lista de salto. :

En la figura anterior, el nivel 1, el nivel 2 y el nivel 3 son los niveles de la lista de omisión. Cada nivel tiene un puntero al siguiente elemento del mismo nivel. Por ejemplo, cuando recorremos para encontrar el elemento 35 en la figura anterior, hay tres opciones:

  • El primero es ejecutar el puntero del nivel level1, que debe recorrerse 7 veces (1->8->9->12->15->20->35) para encontrar el elemento 35.
  • El segundo es ejecutar el puntero de nivel level2, y solo necesita atravesar 5 veces (1-> 9-> 12-> 15-> 35) para encontrar el elemento 35.
  • El tercer tipo es ejecutar los elementos del nivel level 3. En este momento, solo necesita atravesar 3 veces (1-> 12-> 35) para encontrar el elemento 35, lo que mejora enormemente la eficiencia.

Estructura de almacenamiento de skiplist

Cada nodo en la lista de omisión es un nodo zskiplistNode (en el código fuente server.h):

typedef struct zskiplistNode {
    sds ele;//元素
    double score;//分值
    struct zskiplistNode *backward;//后退指针
    struct zskiplistLevel {//层
        struct zskiplistNode *forward;//前进指针
        unsigned long span;//当前节点到下一个节点的跨度(跨越的节点数)
    } level[];
} zskiplistNode;

复制代码
  • nivel

level es el nivel en la tabla de salto, que es una matriz, lo que significa que un elemento de un nodo puede tener múltiples niveles, es decir, múltiples punteros a otros nodos, y el programa puede elegir la ruta más rápida para mejorar el acceso a través de punteros en diferentes niveles de velocidad.

nivel es un número entre 1 y 32 que se genera aleatoriamente de acuerdo con la ley de potencia cada vez que se crea un nuevo nodo.

  • adelante (puntero hacia adelante)

Cada capa tendrá un puntero al elemento al final de la lista vinculada, y el puntero de avance debe usarse al atravesar el elemento.

  • lapso

El lapso registra la distancia entre dos nodos, cabe señalar que si apunta a NULL, el lapso es 0.

  • hacia atrás (puntero hacia atrás)

A diferencia del puntero de avance, solo hay un puntero de retroceso, por lo que solo puede volver al nodo anterior cada vez (el puntero de retroceso no se dibuja en la figura anterior).

  • elemento (elemento)

El elemento de la tabla de salto es un objeto sds (la versión anterior usaba el objeto redisObject) y el elemento debe ser único y no se puede repetir.

  • puntaje

La puntuación del nodo es un número de coma flotante de tipo doble. En la tabla de saltos, los nodos se organizan en orden ascendente según sus puntuaciones. Las puntuaciones de diferentes nodos se pueden repetir.

La descripción anterior es solo un nodo en la lista de omisión, y múltiples nodos zskiplistNode forman un objeto zskiplist:

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;//跳跃表的头节点和尾结点指针
    unsigned long length;//跳跃表的节点数
    int level;//所有节点中最大的层数
} zskiplist;

复制代码

En este punto, puede pensar que el conjunto ordenado se implementa con este zskiplist, pero de hecho, Redis no usa directamente el zskiplist para implementarlo, sino que usa el objeto zset para envolverlo nuevamente.

typedef struct zset {
    dict *dict;//字典对象
    zskiplist *zsl;//跳跃表对象
} zset;

复制代码

Entonces, al final, si un conjunto ordenado usa codificación de lista de saltos, su estructura de datos se muestra en la siguiente figura:

La clave en el diccionario en la parte superior de la figura anterior corresponde al elemento en el conjunto ordenado (miembro), y el valor corresponde a la puntuación (puntuación). En la parte inferior de la figura anterior, los números enteros 1, 8, 9 y 12 de la tabla de salto también corresponden al elemento (miembro), y el número de tipo doble en la última fila es la puntuación (puntuación).

Es decir, los datos en el diccionario y la tabla de salto apuntan a los elementos que almacenamos (las dos estructuras de datos finalmente apuntan a la misma dirección, por lo que los datos no se almacenarán de manera redundante). ¿Por qué Redis hace esto?

¿Por qué elegir usar un diccionario y una tabla de salto?

Los conjuntos ordenados se pueden implementar usando la tabla de salto directamente o usando solo el diccionario, pero pensemos en ello, si usamos solo la tabla de salto, aunque podemos usar un puntero con un lapso grande para recorrer los elementos para encontrar los datos que necesitamos, es complicado. El grado todavía llega a O (logN), y la complejidad de obtener un elemento en un diccionario es O (1). Si usa un diccionario solo para obtener elementos, es muy rápido, pero el diccionario está desordenado, por lo tanto, si desea encontrar un rango, necesita Ordenar es una operación que requiere mucho tiempo, por lo que Redis combina dos estructuras de datos para maximizar el rendimiento, que también es la sutileza del diseño de Redis.

codificación ziplist

Las listas comprimidas se utilizan tanto en objetos de lista como en objetos hash. Para obtener más información, haga clic aquí.

blog.csdn.net/zwx900102/a…

conversión de codificación ziplist y skiplist

Cuando un objeto de conjunto ordenado cumple con las dos condiciones siguientes, utilizará la codificación ziplist para el almacenamiento:

  • El número de elementos almacenados en el objeto de conjunto ordenado es inferior a 128 (se puede modificar configurando zset-max-ziplist-entries).
  • La longitud total de todos los elementos almacenados en el objeto de conjunto ordenado es inferior a 64 bytes (se puede modificar configurando zset-max-ziplist-value).

Comandos comunes de objetos de colección ordenados

  • zadd clave puntuación1 miembro1 puntuación2 miembro2: agregue uno o más elementos (miembros) y sus puntuaciones a la clave del conjunto ordenado.
  • zscore key member: devuelve la puntuación del miembro del miembro en la clave del conjunto ordenado.
  • zincrby clave num miembro: agregue num al miembro en la clave del conjunto ordenado, y num puede ser un número negativo.
  • zcount key min max: Devuelve el número de miembros cuyo valor de puntuación está en el intervalo [min, max] en la clave del conjunto ordenado.
  • zrange key start stop: Devuelve todos los miembros en el intervalo [start, stop] después de que la puntuación en la clave del conjunto ordenado se organiza de menor a mayor.
  • zrevrange key start stop: Devuelve todos los miembros en el intervalo [start, stop] después de que las puntuaciones en la clave del conjunto ordenado se ordenen de mayor a menor.
  • zrangebyscore key min max: Devuelve todos los elementos en el intervalo [min,max] en el conjunto ordenado ordenado por puntuación de menor a mayor. Tenga en cuenta que el valor predeterminado es un intervalo cerrado, pero puede agregar ( o [ antes de los valores de max y min para controlar el intervalo abierto y cerrado.
  • zrevrangebyscore key max min: Devuelve todos los elementos en el intervalo [min,max] en el conjunto ordenado por puntuación de mayor a menor. Tenga en cuenta que el valor predeterminado es un intervalo cerrado, pero puede agregar ( o [ antes de los valores de max y min para controlar el intervalo abierto y cerrado.
  • Miembro clave de zrank: devuelve el rango (de menor a mayor) de los elementos del miembro en el conjunto ordenado, y el resultado devuelto comienza desde 0.
  • Miembro clave de zrevrank: devuelve la clasificación de los elementos del miembro en el conjunto ordenado (de mayor a menor), y el resultado devuelto comienza desde 0.
  • zlexcount key min max: Devuelve el número de miembros entre min y max en el conjunto ordenado. Tenga en cuenta que min y max en este comando deben estar precedidos por ( o [ para controlar el intervalo abierto y cerrado, y los valores especiales - y + representan infinito negativo e infinito positivo, respectivamente.

Conociendo los comandos comunes para operar objetos establecidos ordenados, podemos verificar el tipo y la codificación del objeto hash mencionado anteriormente.Antes de probar, para evitar la interferencia de otros valores clave, primero ejecutamos el comando flushall para borrar la base de datos de Redis.

Antes de ejecutar el comando, primero modificamos el parámetro zset-max-ziplist-entries en el archivo de configuración a 2 y luego reiniciamos el servicio Redis.

Una vez que se complete el reinicio, ejecute los siguientes comandos en secuencia:

zadd name 1 zs 2 lisi //设置 2 个元素会使用 ziplist
type name //查看类型
object encoding name //查看编码 
    
zadd address 1 beijing 2 shanghai 3 guangzhou 4 shenzhen  //设置4个元素则会使用 skiplist编码
type address  //查看类型
object encoding address //查看编码 

复制代码

Consigue el siguiente efecto:

Resumir

Este documento analiza principalmente los principios de implementación de las estructuras de almacenamiento subyacentes intset y skiplist de objetos de conjunto y objetos de conjunto ordenados, y se centra en cómo los conjuntos ordenados implementan la clasificación y por qué se utilizan dos estructuras de datos (diccionario y lista de omisión) para el almacenamiento simultáneo. los datos.


Autor:
Official Account_IT Hermano Enlace: https://juejin.cn/post/7075575482669858824
 

Supongo que te gusta

Origin blog.csdn.net/wdjnb/article/details/124475826
Recomendado
Clasificación