Redis Utilice el tipo de cadena con precaución.

¿Usa String con precaución?

Antes de abrir, haga una comparación grupal:

1. 这是执行 flushdb 后的干净的 Redis 内存使用信息。

127.0.0.1:6379> info memory
# Memory
used_memory:502272
used_memory_human:490.50K
used_memory_rss:7901184
used_memory_peak:119628904
used_memory_peak_human:114.09M
used_memory_lua:33792
mem_fragmentation_ratio:15.73
mem_allocator:jemalloc-3.6.0

2. 执行100000次(一百万)循环执行 set 操作。

// 代码仅作参考
$redisHandle = Redis::connection('intranet_log_redis');
for ($i= 0; $i < 1000000; $i++) {
    
    
    $redisHandle->set($i, 10000000);
}

// 执行步骤2后的内存信息。
127.0.0.1:6379> info memory
# Memory
used_memory:72891064
used_memory_human:69.51M
used_memory_rss:80601088
used_memory_peak:134217744
used_memory_peak_human:128.00M
used_memory_lua:33792
mem_fragmentation_ratio:1.11
mem_allocator:jemalloc-3.6.0

// 由上信息可知除去本身redis占用的内存,一百万个键值对使用了69M的内存。

3. 再次执行 flushdb 后,改为hash类型存储,再次执行1000000(一百万)次循环。

// 代码仅供参考
$redisHandle = Redis::connection('intranet_log_redis');
for ($i= 0; $i < 1000000; $i++) {
    
    
    $redisHandle->hset('testKey', $i, 10000000);
}

// 执行步骤3后查看内存信息
127.0.0.1:6379> info memory
# Memory
used_memory:72883104
used_memory_human:3.43M
used_memory_rss:82509824
used_memory_peak:134217744
used_memory_peak_human:128.00M
used_memory_lua:33792
mem_fragmentation_ratio:1.13
mem_allocator:jemalloc-3.6.0

当当当! 使用了hash存储,内存使用减少了 20 倍!!!这是为什么尼???
Estructura de almacenamiento de tipo cadena

¿Por qué la cadena es 20 veces más grande que el hash cuando se almacenan los mismos datos con la misma cantidad de datos? Con esta pregunta y luego aprenda la implementación específica de string.

En el ejemplo anterior, la parte del valor es 1000000, que se puede almacenar en un tipo Long de 8 bytes. La clave es 0 ~ 1000000 y también se puede almacenar en Long. En teoría, el almacenamiento solo requiere unos pocos megabytes de memoria, ¿por qué se usan 69 megabytes?

Esto está diseñado para codificar y estructurar cadenas, por ejemplo, un sistema de 64 bits guarda un número entero. Redis utilizará un tipo Long de 8 bytes para el almacenamiento, que es el método de codificación int.

La cadena no es la misma, utilizando SDS (Cadena dinámica simple) almacenamiento de estructura de cadena dinámica simple.

estructura Talla descripción
len 4B 4 bytes, indicando la longitud utilizada de buf
alloc 4B 4 bytes, que representan la longitud real asignada de buf, generalmente mayor que len
buf Array, conserve los datos reales. Conecte automáticamente un "\ 0" detrás de la matriz para marcar el final de los datos. Ocupa 1 byte

Se puede ver en la tabla anterior que SDS tendrá una sobrecarga de almacenamiento adicional de len y alloc, que es un poco similar a Mogodb. Por supuesto, para el tipo de cadena, además de la sobrecarga de SDS, también existe la estructura RedisObject. Su función es que los metadatos utilizados para registrar los datos apuntan a estos datos al mismo tiempo,

RedisObject contiene metadatos de 8 bytes y un puntero de 8 bytes (la ubicación real de los datos). Por supuesto, para ahorrar espacio en la memoria, Redis también hizo un diseño especial para el diseño de la memoria de los enteros de tipo Long y SDS.

Por ejemplo: cuando se almacena el entero de tipo Long, el puntero en RedisObject se asigna directamente a los datos del entero, por lo que no es necesario que los punteros adicionales apunten al entero, lo que ahorra el espacio sobre el puntero.

Cuando se almacenan los datos de la cadena, y la cadena es menor o igual a 44 bytes, los metadatos, el puntero y la SDS en RedisObject son un área de memoria contigua, por lo que se puede evitar la fragmentación de la memoria. Este método de diseño también se denomina codificación embstr.
Cuando la cadena tiene más de 44 bytes, la cantidad de datos en SDS comienza a aumentar y Redis ya no presenta SDS y RedisObject juntos, sino que asigna espacio independiente a SDS y usa un puntero para señalar la estructura de SDS. Este método de diseño se denomina modo de codificación sin formato.

La lista anterior es la sobrecarga de almacenamiento adicional aportada por la estructura de cadena y RedisObject, pero este no es el único punto. Redis utiliza una tabla hash global para almacenar todos los pares clave-valor. Cada elemento de la tabla hash es una estructura dictEntry que apunta a un par clave-valor. Hay tres punteros de 8 bytes en la estructura dictEntry, que apuntan a la clave, el valor y el siguiente dictEntry respectivamente. Los tres punteros suman 24 bytes, como se muestra en la siguiente figura: Los
Inserte la descripción de la imagen aquí
tres punteros anteriores comparten 24 bytes, pero de hecho Redis Le asignará 32 bytes, lo que está relacionado con la asignación de memoria jemalloc utilizada por Redis. Cuando jemalloc asigna memoria, encontrará una potencia de 2 mayor que N pero más cercana a N como espacio asignado de acuerdo con el número de bytes que solicitamos N, lo que puede reducir el número de asignaciones frecuentes. Entonces, solo 32 está cerca de 24 y más grande que 24.

Entonces, las dos razones anteriores son que 69M se usa para almacenar un millón de datos, porque hay muchos lugares que generan una sobrecarga de memoria adicional. Entonces, ¿por qué es mucho más pequeño cuando se usa hash?

Almacenamiento de hash

Redis tiene una estructura de datos de bajo nivel llamada ziplist, que es una estructura que ahorra mucha memoria. El encabezado de la lista comprimida tiene tres campos: zlbytes, zltail y zllen, que representan respectivamente la longitud de la lista, el desplazamiento del final de la lista y el número de entradas en la lista. También hay una zlend al final de la lista comprimida, que indica el final de la lista. Como se muestra en la figura: La
Lista comprimida
razón por la que la lista comprimida puede ahorrar memoria es que usa una serie de entradas consecutivas para guardar datos. No se utilizan punteros adicionales para la conexión, por lo que se ahorra espacio. Los metadatos de cada entrada incluyen las siguientes partes.

estructura descripción
prev_len , Que indica la longitud de la entrada anterior. Hay dos valores para prev_len: 1 byte o 5 bytes. Cuando el valor es de 1 byte, significa que la longitud de la entrada anterior es inferior a 254 bytes. Aunque el rango de valores que puede ser representado por un valor de 1 byte es de 0 a 255, el valor predeterminado de zlend en la lista comprimida es 255. Por lo tanto, 255 se usa para indicar el final de toda la lista comprimida por defecto, y otros los lugares que indican la longitud no se pueden utilizar. Este valor es 255. Por lo tanto, cuando la longitud de la entrada anterior es inferior a 254 bytes, el valor de prev_len es 1 byte; de ​​lo contrario, el valor es 5 bytes.
len Auto-longitud 4 bytes
codificacion Método de codificación 1 byte
contenido Almacenar datos reales

Por supuesto, las dos estructuras de implementación subyacentes del tipo Hash son listas comprimidas y tablas hash. El uso apropiado de tablas hash o listas comprimidas está controlado por los siguientes dos conjuntos de parámetros.
hash-max-ziplist-entries : indica el número máximo de elementos en el conjunto de hash al guardar en una lista comprimida. El valor predeterminado es 512.
hash-max-ziplist-value : indica la longitud máxima de un solo elemento en el conjunto de hash cuando se guarda en una lista comprimida. El valor predeterminado es 60.

Cuando el número de elementos escritos en el conjunto Hash excede hash-max-ziplist-entries, o el tamaño de un solo elemento escrito excede hash-max-ziplist-value, Redis cambiará automáticamente la estructura de implementación del tipo Hash de la comprimida lista Convierta a una tabla hash. Una vez que la lista comprimida se convierte en una tabla hash, el tipo de hash siempre se almacenará en la tabla hash y no se volverá a convertir a la lista comprimida. En términos de ahorro de espacio en la memoria, las tablas hash no son tan eficientes como las listas comprimidas.

Ajusté las entradas de hash-max-ziplist a 1 millón cuando estaba experimentando en el artículo. Por lo tanto, debe tener cuidado al almacenar una gran cantidad de pares clave-valor y pensar más en ello. Por ejemplo, se puede utilizar un método de codificación secundario de tipo Hash.

Supongo que te gusta

Origin blog.csdn.net/m0_51504545/article/details/109372792
Recomendado
Clasificación