Qué hacer si la distribución de hash en Redis es desigual

Prefacio

Redis es una base de datos de pares clave-valor, cuyas claves se almacenan mediante hashes. El Redis completo se puede considerar como un hash externo. La razón por la que se llama hash externo es porque Redis también proporciona un tipo de hash internamente, que se puede llamar hash interno. Cuando usamos objetos hash para el almacenamiento de datos, para todo Redis, pasa por dos capas de almacenamiento de hash.

 

Objeto hash

El objeto hash en sí mismo también es una estructura de almacenamiento de valor clave, y la estructura de almacenamiento subyacente también se puede dividir en dos tipos: ziplist (lista comprimida) y hashtable (tabla hash). Estas dos estructuras de almacenamiento también se distinguen por la codificación:

Valor de retorno del comando de codificación de objeto descripción de atributo de codificación OBJ_ENCODING_ZIPLIST Usar lista comprimida para implementar lista zip de objetos hash OBJ_ENCODING_HT Usar diccionario para implementar tabla hash de objetos hash

 

tabla de picadillo

El valor clave en Redis está envuelto por el objeto dictEntry, y la tabla hash se obtiene empaquetando nuevamente el objeto dictEntry. Este es el objeto de tabla hash dictht:

typedef struct dictht {    dictEntry **table;//哈希表数组    unsigned long size;//哈希表大小    unsigned long sizemask;//掩码大小,用于计算索引值,总是等于size-1    unsigned long used;//哈希表中的已有节点数} dictht;复制代码

Nota: La tabla en la definición de estructura anterior es una matriz, cada elemento del cual es un objeto dictEntry.

 

diccionario

Un diccionario, también conocido como tabla de símbolos, matriz asociativa o mapa, tiene un objeto dictht de tabla hash anidado dentro del diccionario. La siguiente es la definición de un diccionario ht:

typedef struct dictType {    uint64_t (*hashFunction)(const void *key);//计算哈希值函数    void *(*keyDup)(void *privdata, const void *key);//复制键函数    void *(*valDup)(void *privdata, const void *obj);//复制值函数    int (*keyCompare)(void *privdata, const void *key1, const void *key2);//对比键函数    void (*keyDestructor)(void *privdata, void *key);//销毁键函数    void (*valDestructor)(void *privdata, void *obj);//销毁值函数} dictType;复制代码

Entre ellos, dictType define algunas funciones de uso común, y su estructura de datos se define de la siguiente manera:

typedef struct dictType {    uint64_t (*hashFunction)(const void *key);//计算哈希值函数    void *(*keyDup)(void *privdata, const void *key);//复制键函数    void *(*valDup)(void *privdata, const void *obj);//复制值函数    int (*keyCompare)(void *privdata, const void *key1, const void *key2);//对比键函数    void (*keyDestructor)(void *privdata, void *key);//销毁键函数    void (*valDestructor)(void *privdata, void *obj);//销毁值函数} dictType;复制代码

Cuando creamos un objeto hash, podemos obtener el siguiente diagrama (se omiten algunos atributos):

Qué hacer si la distribución de hash de Redis es desigual

 

operación de refrito

Una matriz ht [2] se define en dict, y dos tablas hash se definen en ht [2]: ht [0] y ht [1]. Redis solo usará ht [0] por defecto, y no usará ht [1], ni asignará espacio para la inicialización de ht [1].

Cuando se configura un objeto hash, en qué subíndice de la matriz hash (dictEntry [3] en la figura anterior) se determinará calculando el valor hash. Si se produce una colisión de hash (el valor de hash calculado es el mismo), entonces el mismo subíndice tendrá múltiples dictEntry, formando así una lista vinculada (el punto más a la derecha en la figura anterior apunta a la posición NULL), pero debe tenerse en cuenta que la última inserción El elemento siempre está en la parte superior de la lista vinculada (es decir, cuando ocurre un conflicto de hash, el nodo siempre se coloca en la parte superior de la lista vinculada).

Al leer datos, cuando encuentra un nodo con varios elementos, debe recorrer la lista vinculada, por lo que cuanto más larga sea la lista vinculada, peor será el rendimiento. Para garantizar el rendimiento de la tabla hash, la tabla hash debe repetirse cuando se cumple una de las dos condiciones siguientes:

  • Cuando el factor de carga es mayor o igual a 1 y dict_can_resize es 1.
  • Cuando el factor de carga es mayor o igual que el umbral de seguridad (dict_force_resize_ratio = 5).

PS: factor de carga = número de nodos usados ​​en la tabla hash / tamaño de la tabla hash (es decir: h [0] .used / h [0] .size).

 

paso de refrito

El hash de expansión y el hash de reducción se completan ejecutando el refrito, que implica la asignación y liberación de espacio, principalmente a través de los siguientes cinco pasos:

  1. Asignar espacio para la tabla hash ht [1] del diccionario dict, cuyo tamaño depende del número de nodos guardados en la tabla hash actual (es decir: ht [0] .used):
  2. Establezca el valor del atributo rehashix en el diccionario en 0, lo que indica que se está realizando la operación de refrito.
  3. Vuelva a calcular los valores hash de todos los pares clave-valor en ht [0] y colóquelos en la posición correspondiente de la matriz ht [1]. El valor de rehashix debe incrementarse en 1 después de completar el refrito de un par clave-valor.
  4. Cuando todos los pares clave-valor en ht [0] se migren a ht [1], suelte ht [0], cambie ht [1] a ht [0] y luego cree una nueva matriz ht [1]. Para preparar para el próximo refrito.
  5. Establezca el atributo rehashix en el diccionario en -1, lo que significa que esta operación de refrito ha terminado y espere al siguiente refrito.

 

Refrito progresivo

Esta operación de refrito en Redis no es un  refrito de una sola vez, sino un refrito lento  del par clave-valor en  ht [0]  a  ht [1] varias veces  , por lo que esta operación también se denomina Refresco progresivo  . El refrito progresivo puede evitar la gran cantidad de cálculos que conlleva el refrito centralizado, que es una idea de divide y vencerás.

En el proceso de refrito progresivo, debido a que puede haber nuevos pares clave-valor almacenados, en este momento ** El enfoque de Redis es colocar los pares clave-valor recién agregados en ht [1] de manera uniforme, para garantizar ht [0] La cantidad de pares clave-valor solo disminuirá **.

Cuando la operación se está realizando refrito, si el servidor recibe un comando de la operación de solicitud del cliente, primero consultará  ht [0] , luego los resultados se verán menos que ht [1]  consulta .

 

ziplist

Algunas características de ziplist se analizaron por separado en el artículo anterior. Si desea obtener más información, puede hacer clic aquí. Pero debe tenerse en cuenta que la diferencia entre ziplist en el objeto hash y ziplist en el objeto list es que el objeto hash es una forma de valor-clave, por lo que la lista zip también aparece como un valor-clave, y la clave y valor están muy juntos:

Qué hacer si la distribución de hash de Redis es desigual

 

conversión de codificación ziplist y hashtable

Cuando un objeto hash puede cumplir cualquiera de las dos condiciones siguientes, el objeto hash elegirá utilizar la codificación ziplist para el almacenamiento:

  • La longitud total de todos los pares clave-valor en el objeto hash (incluidas claves y valores) es menor o igual a 64 bytes (este umbral se puede controlar mediante el parámetro hash-max-ziplist-value).
  • El número de pares clave-valor en el objeto hash es menor o igual a 512 (este umbral se puede controlar mediante el parámetro hash-max-ziplist-entries).

Una vez que no se cumple alguna de estas dos condiciones, el objeto hash elegirá utilizar la codificación de tabla hash para el almacenamiento.

 

Comandos comunes para objetos hash

  • hset key field value: establece un solo campo (valor de clave del objeto hash).
  • hmset key field1 value1 field2 value2: Establezca varios campos (valores clave de objetos hash).
  • hsetnx key field value: establece el valor del campo de campo en la clave de la tabla hash en value. Si el campo ya existe, no se realiza ninguna operación.
  • hget key field: Obtiene el valor correspondiente al campo del campo en la clave de la tabla hash.
  • hmget key field1 field2: Obtiene el valor correspondiente a varios campos en la clave de la tabla hash.
  • hdel key field1 field2: Elimina uno o más campos en la clave de la tabla hash.
  • hlen key: Devuelve el número de campos en la clave de la tabla hash.
  • Incremento de campo de clave hincrby: agregue incremento al valor del campo de campo en la clave de tabla hash. El incremento puede ser un número negativo. Si el campo no es un número, se informará un error.
  • Incremento de campo clave hincrbyfloat: Agrega incremento al valor del campo de campo en la clave de la tabla hash. El incremento puede ser un número negativo. Si el campo no es de tipo flotante, se informará un error.
  • hkeys key: Obtiene todos los campos en la clave de la tabla hash.
  • Clave hvals: Obtiene los valores de todos los campos en la tabla hash.

Al conocer los comandos comunes para operar objetos hash, podemos verificar el tipo y la codificación de los objetos hash mencionados anteriormente. Para evitar la interferencia de otros valores clave antes de la prueba, primero ejecutamos el comando flushall para borrar la base de datos de Redis.

Luego, ejecute los siguientes comandos en secuencia:

hset address country chinatype addressobject encoding address复制代码

Obtenga los siguientes efectos:

Qué hacer si la distribución de hash de Redis es desigual

 

Puede ver que cuando solo hay un par clave-valor en nuestro objeto hash, la codificación subyacente es ziplist.

Ahora cambiamos el parámetro hash-max-ziplist-entries a 2, luego reiniciamos Redis y finalmente ingresamos el siguiente comando para probar:

hmset key field1 value1 field2 value2 field3 value3object encoding key复制代码

Después de la salida, se obtienen los siguientes resultados:

Qué hacer si la distribución de hash de Redis es desigual

 

Como puede ver, la codificación se ha convertido en una tabla hash.

 

para resumir

Este artículo presenta principalmente el uso de hashtable, la estructura de almacenamiento subyacente del tipo de hash entre los cinco tipos de datos comúnmente utilizados en Redis y cómo Redis realiza el re-hash cuando la distribución del hash es desigual. Finalmente, aprendí sobre algunos hash de uso común objetos Ordene y verifique la conclusión de este artículo a través de algunos ejemplos.

Supongo que te gusta

Origin blog.csdn.net/Java0258/article/details/112990267
Recomendado
Clasificación