Interesante Redis: ¿Por qué Redis de un solo subproceso puede admitir 10w + QPS?

Inserte la descripción de la imagen aquí

¿Por qué un solo hilo puede admitir 10w + QPS?

A menudo escuchamos que Redis es un programa de un solo subproceso. Para ser precisos, Redis es un programa de múltiples subprocesos, pero la parte de procesamiento de solicitudes se implementa mediante un subproceso.

Los resultados de la prueba de Alibaba Cloud en Redis QPS son los siguientes:
Inserte la descripción de la imagen aquí
¿Cómo usa Redis un solo hilo para lograr un QPS de 10w + por segundo?

  1. Utilice multiplexación de E / S
  2. Tareas que no requieren un uso intensivo de la CPU
  3. Operación de memoria pura
  4. Estructura de datos eficiente

¿Cómo manejar múltiples conexiones de clientes con un solo hilo?

Esto tiene que mencionar la tecnología de multiplexación IO, es decir, NIO en Java.

Cuando usamos IO de bloqueo (BIO en Java), llamamos a la función de lectura y pasamos el parámetro n, lo que significa que el hilo regresará después de leer n bytes, de lo contrario será bloqueado. El método de escritura generalmente no bloquea, a menos que el búfer de escritura esté lleno, la escritura se bloqueará hasta que se libere espacio en el búfer.

Cuando utilizamos la tecnología de multiplexación IO, cuando no hay datos para leer o escribir, el hilo del cliente volverá directamente sin bloquearse. De esta manera, Redis puede usar un hilo para monitorear múltiples Sockets.Cuando un Socket es legible o escribible, Redis lee la solicitud, manipula los datos en la memoria y luego regresa.

Cuando se usa un solo subproceso, la CPU de varios núcleos no se puede usar, pero la mayoría de los comandos en Redis no son tareas que requieren un uso intensivo de la CPU, por lo que la CPU no es el cuello de botella de Redis .

Alta simultaneidad y gran volumen de datos, amplíe el cuello de botella de Redis que se refleja principalmente en la memoria y el ancho de banda de la red, por lo que ve Redis para ahorrar memoria, la memoria ocupada por la estructura de datos subyacente puede ser la menor posible, y uno el tipo de datos es diferente Se utilizarán diferentes estructuras de datos en diferentes escenarios.

Inserte la descripción de la imagen aquí

Por lo tanto, Redis puede manejar una gran cantidad de solicitudes con un solo hilo, por lo que no es necesario utilizar varios hilos . Además, el uso de un solo hilo tiene las siguientes ventajas

  1. Sin la sobrecarga de rendimiento del cambio de hilo
  2. No es necesario bloquear varias operaciones (si se utilizan subprocesos múltiples, es necesario bloquear el acceso a los recursos compartidos, lo que aumenta la sobrecarga)
  3. Fácil de depurar, alta capacidad de mantenimiento

Finalmente, Redis es una base de datos en memoria, y las operaciones de lectura y escritura de varios comandos se realizan en función de la memoria . Todo el mundo sabe que la eficiencia de la memoria operativa y los discos operativos difiere en varios órdenes de magnitud. Aunque Redis es muy eficiente, todavía hay algunas operaciones lentas que debe evitar

¿Cuáles son las operaciones lentas de Redis?

Inserte la descripción de la imagen aquí
Varios comandos de Redis se ejecutan en secuencia en un hilo. Si un comando se ejecuta en Redis durante demasiado tiempo, afectará el rendimiento general, porque las solicitudes posteriores no se procesarán hasta que se procese la solicitud anterior. Estos La operación que consume mucho tiempo ha las siguientes partes

Redis puede registrar esos comandos que consumen mucho tiempo a través de registros, solo use la siguiente configuración

# 命令执行耗时超过 5 毫秒,记录慢日志
CONFIG SET slowlog-log-slower-than 5000
# 只保留最近 500 条慢日志
CONFIG SET slowlog-max-len 500

Ejecute el siguiente comando, puede consultar el registro lento registrado recientemente

127.0.0.1:6379> SLOWLOG get 5
1) 1) (integer) 32693       # 慢日志ID
   2) (integer) 1593763337  # 执行时间戳
   3) (integer) 5299        # 执行耗时(微秒)
   4) 1) "LRANGE"           # 具体执行的命令和参数
      2) "user_list:2000"
      3) "0"
      4) "-1"
2) 1) (integer) 32692
   2) (integer) 1593763337
   3) (integer) 5044
   4) 1) "GET"
      2) "user_info:1000"
...

Usa comandos demasiado complejos

En el artículo anterior, presentamos la estructura de datos subyacente de Redis, y su complejidad de tiempo se muestra en la siguiente tabla.

nombre complejidad del tiempo
dict (diccionario) O (1)
ziplist (lista comprimida) Sobre)
zskiplist (lista de omisión) O (logN)
lista rápida (lista rápida) Sobre)
intset (conjunto de enteros) Sobre)

Operación de un solo elemento : la operación de adición, eliminación, modificación y consulta de elementos en la colección está relacionada con la estructura de datos subyacente. Por ejemplo, la complejidad temporal de agregar, eliminar, modificar y consultar un diccionario es O (1), y la complejidad de tiempo de agregar, eliminar y consultar una tabla de salto es O (logN)

Operación de rango : atraviesa la colección, como HGETALL de tipo Hash, SMEMBERS de tipo Set, LRANGE de tipo List, ZRANGE de tipo ZSet, la complejidad de tiempo es O (n), evita usarlo y usa comandos de la serie SCAN en su lugar. (Hscan para hash, sscan para set, zscan para zset)

Operaciones de agregación : la complejidad temporal de este tipo de operación suele ser mayor que O (n), como SORT, SUNION, ZUNIONSTORE

Operación estadística : cuando desea obtener la cantidad de elementos en el conjunto, como LLEN o SCARD, la complejidad del tiempo es O (1), porque sus estructuras de datos subyacentes, como quicklist, dict, intset almacenan la cantidad de elementos.

Operación de límite : la capa inferior de la lista se implementa mediante lista rápida. La lista rápida guarda los nodos principales y finales de la lista vinculada. Por lo tanto, la complejidad temporal de la operación en los nodos principales y finales de la lista vinculada es O (1) , como LPOP, RPOP, LPUSH, RPUSH

Cuando desee obtener la clave en Redis, evite usar claves * . Los pares clave-valor almacenados en Redis se almacenan en un diccionario (similar a HashMap en Java, pero también implementado a través de una matriz + lista vinculada), el tipo de clave Todos son cadenas y el tipo de valor puede ser cadena, conjunto, lista, etc.

Por ejemplo, cuando ejecutamos el siguiente comando, la estructura del diccionario de redis es la siguiente

set bookName redis;
rpush fruits banana apple;

Inserte la descripción de la imagen aquí
Podemos usar el comando keys para consultar una clave específica en Redis, como se muestra a continuación

# 查询所有的key
keys *
# 查询以book为前缀的key
keys book*

La complejidad del comando de claves es O (n). Atravesará todas las claves en el diccionario. Si hay muchas claves en Redis, todas las instrucciones para leer y escribir en Redis se retrasarán, así que no use esto en un ambiente de producción Pedido (si está listo para partir, le deseo un buen momento).

Dado que no se le permite usar claves, debe haber una alternativa, que es escanear

En comparación con las teclas, el escaneo tiene las siguientes características

  1. Aunque la complejidad también es O (n), se ejecuta a través de la distribución del cursor y no bloquea el hilo.
  2. Igual que las teclas, proporciona la función de coincidencia de patrones
  3. Desde el comienzo del recorrido completo hasta el final del recorrido completo, todos los elementos que han estado en el conjunto de datos serán devueltos por el recorrido completo, pero el mismo elemento puede devolverse varias veces.
  4. Si un elemento se agrega al conjunto de datos durante la iteración, o se elimina del conjunto de datos durante la iteración, el elemento puede o no ser devuelto.
  5. El resultado devuelto está vacío no significa el final del recorrido, sino que depende de si el valor del cursor devuelto es 0

Los amigos interesados ​​pueden analizar la implementación del código fuente de escaneo para comprender estas características

Utilice zscan para atravesar zset, hscan para atravesar hash, sscan para atravesar set es similar al comando scan, porque las estructuras de datos subyacentes de hash, set y zset tienen dict.

Operación bigkey

Si el valor correspondiente a una clave es muy grande, entonces la clave se llama bigkey. Escribir en bigkey tarda más en asignar memoria. Del mismo modo, eliminar bigkey para liberar memoria también lleva más tiempo

Si encuentra un comando de baja complejidad como SET / DEL en el registro lento, debe verificar si es causado por escribir en bigkey.

¿Cómo localizar a Bigkey?

Redis proporciona comandos para escanear bigkey

$ redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.01

...
-------- summary -------

Sampled 829675 keys in the keyspace!
Total key length in bytes is 10059825 (avg len 12.13)

Biggest string found 'key:291880' has 10 bytes
Biggest   list found 'mylist:004' has 40 items
Biggest    set found 'myset:2386' has 38 members
Biggest   hash found 'myhash:3574' has 37 fields
Biggest   zset found 'myzset:2704' has 42 members

36313 strings with 363130 bytes (04.38% of keys, avg size 10.00)
787393 lists with 896540 items (94.90% of keys, avg size 1.14)
1994 sets with 40052 members (00.24% of keys, avg size 20.09)
1990 hashs with 39632 fields (00.24% of keys, avg size 19.92)
1985 zsets with 39750 members (00.24% of keys, avg size 20.03)

Puede ver que la entrada del comando tiene las siguientes 3 partes

  1. El número de teclas en la memoria, la memoria total ocupada, la memoria promedio ocupada por cada tecla
  2. La memoria máxima ocupada por cada tipo, el nombre de la clave ha sido
  3. El porcentaje de cada tipo de datos y el tamaño medio.

El principio de este comando es que redis ejecuta el comando de escaneo internamente, atraviesa todas las claves en la instancia y luego ejecuta los comandos strlen, llen, hlen, scar, zcard para obtener la longitud del tipo de cadena y el tipo de contenedor (número de elementos en lista, hash, conjunto, zset)

Utilice este comando para prestar atención a los dos problemas siguientes

  1. Al realizar un escaneo de bigkey en instancias en línea, para evitar un aumento repentino en las operaciones (operación por segundo), se puede agregar un parámetro de suspensión a través de -i. El significado anterior es que cada 100 instrucciones de escaneo se suspenderán durante 0.01 s
  2. Para los tipos de contenedor (lista, hash, set, zset), la clave escaneada es la clave con más elementos, pero una clave tiene una gran cantidad de elementos, lo que no significa necesariamente que ocupe más memoria.

¿Cómo solucionar los problemas de rendimiento causados ​​por bigkey?

  1. Trate de evitar escribir a bigkey
  2. Si está usando redis4.0 o superior, puede usar el comando unlink en lugar de del. Este comando puede liberar la operación de memoria clave al hilo de fondo para su ejecución.
  3. Si está utilizando redis 6.0 o superior, puede activar el mecanismo lazy-free (lazyfree-lazy-user-del yes), y cuando se ejecute el comando del, también se ejecutará en un hilo de fondo

Una gran cantidad de claves caducó de forma centralizada

Podemos establecer el tiempo de caducidad de la clave en Redis. Cuando caduque la clave, ¿cuándo se eliminará?

Si escribimos la estrategia de vencimiento de Redis, pensaremos en las siguientes tres soluciones

  1. Eliminación programada, mientras configura el tiempo de vencimiento de la clave, cree un temporizador. Cuando llegue la hora de caducidad de la clave, elimínela inmediatamente
  2. Eliminación perezosa, cada vez que se obtiene una clave, se juzga si la clave está caducada, si caduca, la clave se elimina, si no está caducada, se devuelve la clave
  3. Elimine periódicamente, verifique la clave de vez en cuando, elimine las claves caducadas en ella. La
    estrategia de eliminación de tiempo no es amigable para la CPU. Cuando hay más claves caducadas, el hilo de Redis se usa para eliminar claves caducadas, lo que afectará la respuesta de solicitudes normales.

La estrategia de eliminación de tiempo no es amigable para la CPU. Cuando hay muchas claves caducadas, el hilo de Redis se usa para eliminar claves caducadas, lo que afectará la respuesta de las solicitudes normales.

La eliminación diferida de la CPU leída es mejor, pero desperdiciará mucha memoria. Si una clave establece el tiempo de caducidad y la coloca en la memoria, pero no se accede a ella, siempre existirá en la memoria.

La estrategia de eliminación regular es más amigable para la CPU y la memoria

La estrategia de eliminación de claves caducadas de redis elige las dos siguientes

  1. Eliminación perezosa
  2. Eliminar regularmente

Eliminación diferida Cuando el
cliente accede a la clave, comprueba el tiempo de caducidad de la clave y la elimina inmediatamente si caduca.

Para eliminar
Redis con regularidad , la clave con el tiempo de vencimiento se colocará en un diccionario separado, y el diccionario se recorre con regularidad para eliminar la clave caducada. La estrategia transversal es la siguiente

  1. Realice 10 escaneos caducados por segundo y seleccione al azar 20 claves del diccionario caducado cada vez
  2. Eliminar la clave caducada entre las 20 claves
  3. Si la proporción de claves caducadas excede 1/4, continúe con el paso uno
  4. El límite superior de cada tiempo de escaneo no es más de 25 ms por defecto para evitar atascos de hilo

Debido a que el hilo principal elimina la clave caducada en Redis, para no bloquear la solicitud del usuario, la clave caducada se elimina una pequeña cantidad de veces . El código fuente puede hacer referencia al método activeExpireCycle en expire.c

Para evitar que el hilo principal elimine la clave todo el tiempo, podemos usar las siguientes dos soluciones

  1. Agregue un número aleatorio a las claves que caducan al mismo tiempo para romper el tiempo de caducidad y reducir la presión de borrar la clave.
  2. Si está utilizando redis versión 4.0 o superior, puede activar el mecanismo de lazy-free (lazyfree-lazy-expire yes). Cuando se elimina la clave caducada, la operación de liberación de memoria se ejecutará en el hilo de fondo.

La memoria alcanza el límite superior, lo que activa la estrategia de eliminación.

Inserte la descripción de la imagen aquí
Redis es una base de datos en memoria. Cuando la memoria utilizada por Redis excede el límite de la memoria física, los datos de la memoria se intercambiarán con frecuencia con el disco y el intercambio hará que el rendimiento de Redis disminuya drásticamente. Entonces, en el entorno de producción, limitamos la cantidad de memoria utilizada configurando el parámetro maxmemoey.

Cuando la memoria real utilizada excede maxmemoey, Redis proporciona las siguientes estrategias opcionales.

noeviction: la solicitud de escritura devuelve un error

volatile-lru: use el algoritmo lru para eliminar los pares clave-valor con un tiempo de vencimiento
volatile-lfu: use el algoritmo lfu para eliminar los pares clave-valor con un tiempo de vencimiento
volatile-random: elimine aleatoriamente
volatile del clave-valor se empareja con un tiempo de vencimiento -ttl: eliminar de acuerdo con el orden del tiempo de vencimiento, cuanto antes el vencimiento, antes se eliminará

allkeys-lru: elimina todos los pares clave-valor usando el algoritmo
lru allkeys-lfu: elimina todos los pares clave-valor usando el algoritmo
lfu allkeys-random: elimina aleatoriamente de todos los pares clave-valor

La estrategia de eliminación de Redis también se ejecuta en el hilo principal. Pero después de que la memoria excede el límite superior de Redis, algunas claves deben eliminarse cada vez que se escribe, lo que resulta en un tiempo de solicitud más largo.

Se puede mejorar de las siguientes formas

  1. Aumente la memoria o coloque datos en varias instancias
  2. La estrategia de eliminación se cambia a eliminación aleatoria. En términos generales, la eliminación aleatoria es mucho más rápida que la de lru.
  3. Evite almacenar bigkey y reduzca el consumo de tiempo de liberar memoria

La forma de escribir registros AOF es siempre

Inserte la descripción de la imagen aquí
El mecanismo de persistencia de Redis incluye instantáneas RDB y registros AOF. Después de escribir cada comando, Redis proporciona los siguientes tres mecanismos de cepillado

siempre:
Escritura síncrona, sincronizar al disco después de que se ejecute el comando de escritura cada segundo: escribir de nuevo cada segundo, después de que se ejecute cada comando de escritura, simplemente escriba primero el registro en el búfer de memoria del archivo aof y el contenido del búfer cada 1 segundo Write to disk
no: el sistema operativo controla la escritura. Después de que se ejecuta cada comando de escritura, simplemente escribe el registro en el búfer de memoria del archivo aof y el sistema operativo decide cuándo volver a escribir el contenido del búfer. al disco

Cuando el mecanismo de descarga de AOF está siempre, redis descargará el comando de escritura en el disco antes de regresar cada vez que procese un comando de escritura. Todo el proceso se lleva a cabo en el hilo principal de Redis, lo que inevitablemente ralentizará el rendimiento de redis.

Cuando el mecanismo de parpadeo de AOF es cada segundo, redis regresa después de escribir en la memoria. La operación de parpadeo se ejecuta en el subproceso de fondo y el subproceso de fondo vacía los datos de la memoria en el disco cada segundo.

Cuando el mecanismo de parpadeo de AOF es no, es posible que se pierdan algunos datos después del tiempo de inactividad, por lo que generalmente no se utilizan.

En circunstancias normales, el mecanismo de cepillado aof se configura como everysec.

La bifurcación tarda demasiado

En la sección de persistencia, hemos mencionado que Redis genera archivos RDB y reescritura de registros AOF, los cuales son ejecutados por los subprocesos de la bifurcación del hilo principal, cuanto mayor es la memoria del hilo principal, mayor es el tiempo de bloqueo.

Se puede optimizar de la siguiente manera

  1. Controle el tamaño de la memoria de la instancia de Redis, intente controlarlo dentro de los 10 g, porque cuanto mayor es la memoria, mayor es el tiempo de bloqueo
  2. Configure una estrategia de persistencia razonable, como generar instantáneas RDB en el nodo esclavo

Blog de referencia

[1] http://kaito-kidd.com/2021/01/23/redis-slow-latency-analysis/
[2] https://draveness.me/whys-the-design-redis-single-thread/
escanear 命令
[3] http://jinguoxing.github.io/redis/2018/09/04/redis-scan/
escanear 命令 实现
[4] https://juejin.cn/post/6844903688528461831

Supongo que te gusta

Origin blog.csdn.net/zzti_erlie/article/details/113429308
Recomendado
Clasificación