Red de conocimiento informático - Material del sitio web - Explicación detallada de las preguntas de la entrevista de Dachang: ¿Cómo utilizar Redis para implementar el bloqueo distribuido?

Explicación detallada de las preguntas de la entrevista de Dachang: ¿Cómo utilizar Redis para implementar el bloqueo distribuido?

Hablemos de una pregunta común en las entrevistas:

La respuesta simple es ir al cliente de Redission. El esquema de bloqueo en Redission es un esquema relativamente completo y detallado para el bloqueo distribuido de Redis.

Entonces, ¿por qué el esquema de bloqueo en Redission es tan perfecto?

Tengo mucha experiencia en el uso de Redis para bloqueo distribuido. En la práctica, he usado Redis para explorar muchos escenarios de aplicaciones de bloqueo distribuido y resumí mucha experiencia.

Entonces, antes de explicar por qué los bloqueos de Redis son perfectos, echemos un vistazo a algunos de los problemas que encontré al usar Redis para el bloqueo distribuido.

Utilizo el bloqueo distribuido de Redis para resolver el problema de los usuarios que obtienen cupones. El requisito comercial es el siguiente: después de que un usuario obtiene un cupón, la cantidad de cupones debe reducirse en uno. Si el cupón se agota, el usuario no puede recuperarlo.

Durante la implementación, primero lea el número de cupones de la base de datos para juzgar. Cuando el cupón es mayor que 0, se permite su uso. Luego, reduzca el número de cupones en uno y escriba Atrás. a la base de datos.

En ese momento, debido a la gran cantidad de solicitudes, utilizamos tres servidores para la descarga.

Ahora hay un problema:

Si la aplicación A en uno de los servidores obtiene el número de cupón, pero debido a que está procesando la lógica de negocios, no transfiere el número de cupón en tiempo Actualiza la base de datos; mientras que la Aplicación B en otro servidor actualiza el número de cupón mientras la Aplicación A procesa la lógica de negocios. Luego, cuando la Aplicación A actualiza la cantidad de cupones en la base de datos, sobrescribe la cantidad de cupones que la Aplicación B ha actualizado.

Algunos pueden preguntar por qué no usamos SQL aquí:

La razón es que sin bloqueo distribuido, la cantidad de cupones puede volverse negativa. Porque cuando el número actual de cupones es 1, si dos usuarios inician solicitudes de cupones a través de dos servidores al mismo tiempo, ambos usuarios cumplen la condición de que el cupón sea mayor que 0, y luego ambos usuarios ejecutan SQL y dicen algo, entonces el número de cupones se convertirá directamente en -1.

Otros dicen usar bloqueo optimista, como usar el siguiente SQL:

Es probable que este método conduzca a un tiempo prolongado en un momento determinado en el que los datos no se han actualizado en absoluto. Inténtelo de nuevo.

Por lo tanto, después de una consideración exhaustiva, adoptamos un bloqueo distribuido de Redis para evitar que varios clientes actualicen la cantidad de cupones al mismo tiempo mediante exclusión mutua.

En ese momento, lo primero que pensamos fue en usar el comando setnx de Redis, que en realidad es la abreviatura de set si no existe.

Devolverá 1 cuando la clave se configure correctamente; de ​​lo contrario, devolverá 0. Por lo tanto, un setnx exitoso puede considerarse como una adquisición de bloqueo, mientras que un setnx fallido puede considerarse como una adquisición de bloqueo fallida.

Si deseas desbloquear el bloqueo, ejecuta el comando task del para quitar la llave.

Con esta función, podemos hacer que el sistema ejecute un comando setnx en Redis antes de ejecutar la lógica del cupón. Según los resultados de la ejecución, podemos determinar si se ha adquirido el bloqueo. Si se obtiene el bloqueo, continuamos ejecutando el negocio y luego usamos la instrucción del para liberar el bloqueo después de la ejecución. De lo contrario, deberá esperar un cierto tiempo e intentar adquirir el candado nuevamente.

A primera vista, todo esto parece estar bien y el uso de la instrucción setnx logra el efecto de exclusión mutua deseado.

Sin embargo, esto supone que todo está bien. Los problemas surgen cuando el entorno es anormal.

Piense en lo que sucedería si la aplicación que mantiene el bloqueo fallara repentinamente o el servidor en el que estaba se cayera.

Esto provocará un punto muerto: la aplicación que mantiene el bloqueo no puede liberarlo y otras aplicaciones no tienen posibilidad de obtenerlo. Esto podría causar grandes contratiempos en línea, por lo que tuvimos que mejorar nuestra solución para solucionar este problema.

¿Cómo solucionarlo? Como hemos visto, la causa principal de los interbloqueos es que una vez que algo sale mal con la aplicación que mantiene el bloqueo, no lo libera. Pensándolo desde esta perspectiva, puede establecer un tiempo de vencimiento para la clave en Redis.

En este caso, incluso si algo sale mal, la clave se liberará después de un tiempo. De hecho, eso es exactamente lo que hace la gente.

Sin embargo, dado que el comando setnx en sí no establece un tiempo de espera, hay dos formas de hacerlo:

1. Utilice un script lua y use expire después de usar el comando setnx. El comando establece el tiempo de vencimiento de la clave.

2. Utilice el comando set(key, value, NX, EX, timeout) para configurar el bloqueo y el tiempo de espera simultáneamente.

Se pueden utilizar ambos métodos anteriores.

El script para liberar el bloqueo es el mismo en ambos métodos, llamando directamente al comando Redis del.

Hasta ahora, nuestras cerraduras son mutuamente excluyentes y no causarán interbloqueos debido a problemas con el sistema que sujeta las cerraduras. ¿Es esto perfecto?

Supongamos que nos encontramos con una situación en la que la aplicación que mantiene el bloqueo lo mantiene durante más tiempo que el tiempo de espera que configuramos. Hay dos situaciones:

La primera situación es relativamente normal. Después de todo, su tarea expiró y la clave debería haberse borrado normalmente.

Pero lo más aterrador es el segundo escenario, donde descubres que la clave que configuraste todavía existe. ¿qué significa eso? Esto significa que la clave existente actualmente fue establecida por otra aplicación.

En este momento, si la aplicación que mantiene el bloqueo de tiempo de espera llama al comando del para eliminar el bloqueo, el bloqueo establecido por otros se eliminará por error, causando problemas en el funcionamiento del sistema.

Entonces, para resolver este problema, debemos continuar modificando el script de Redis... Destrúyelo, estoy cansado...

Primero, necesitamos para permitir que la aplicación establezca un valor único que solo la aplicación conoce al adquirir el bloqueo.

Con este valor único, el sistema puede identificar cuando se libera el bloqueo si fue establecido por la propia aplicación. Si es así, el sistema liberará el bloqueo, es decir, eliminará la clave; de ​​lo contrario, el sistema no realizará ninguna acción;

El script es el siguiente:

o

Aquí, ARGV[1] es una variable de parámetro que puede pasar un valor único. Por ejemplo, un valor UUID que solo usted conoce o un algoritmo de bola de nieve que genera una identificación única que solo usted conoce.

El script para liberar el bloqueo se ve así:

Como puede ver, desde una perspectiva empresarial, nuestro bloqueo distribuido satisface las necesidades comerciales reales de todos modos. Pueden ser mutuamente excluyentes, no provocarán un punto muerto, no eliminarán accidentalmente los bloqueos de otras personas y solo se pueden liberar los bloqueos que ellos agregaron.

¡Todo es maravilloso! ! !

¡Desafortunadamente, nuestro sistema no admite estas funciones!

Desafortunadamente, existe otro peligro oculto que aún no hemos eliminado. La trampa es el propio Redis.

Como sabes, los scripts lua se utilizan como singletons para Redis. Si Redis falla, nuestros bloqueos distribuidos no funcionarán y, si no funcionan, tendrá un impacto significativo en el funcionamiento normal del negocio, lo cual es inaceptable para nosotros.

Por lo tanto, necesitamos que Redis tenga alta disponibilidad. En términos generales, la forma de resolver el problema de alta disponibilidad de Redis es utilizar un clúster maestro-esclavo.

Pero el clúster maestro-esclavo traerá nuevos problemas. El principal problema es que Redis experimentará retrasos en la sincronización de los datos maestro-esclavo.

Este retraso creará una condición límite: cuando Redis en el servidor maestro haya establecido un bloqueo, pero los datos del bloqueo aún no se hayan sincronizado con el servidor esclavo, el servidor maestro dejará de funcionar. Posteriormente, el dispositivo esclavo es ascendido a dispositivo maestro. En este momento, el dispositivo esclavo ya no tiene los datos de bloqueo previamente establecidos por el dispositivo maestro; el bloqueo se pierde... ...perdido...

Aquí, finalmente podemos presentar Redission (cliente Redis de código abierto), veamos cómo implementa el bloqueo distribuido de Redis.

La idea de Redission de implementar el bloqueo distribuido es muy simple: ya sea un clúster maestro-esclavo o un clúster de Redis Cluster, ejecutará el script para configurar el bloqueo de Redis en cada Redis en el clúster. por uno En otras palabras, el clúster en cada Redis contendrá datos de bloqueo establecidos.

Pongamos un ejemplo a continuación.

Supongamos que hay 5 máquinas en el clúster de Redis y que se ha evaluado que el tiempo de espera de bloqueo está configurado adecuadamente en 10 segundos.

Paso 1, calculemos el tiempo de espera total para el clúster, que es de 5 segundos (10 segundos / 2 tiempos de espera de bloqueo).

Paso 2: Divide 5 segundos por el número de máquinas, que es 1 segundo. Este segundo es el tiempo de espera aceptable para cada conexión de Redis.

En el paso 3, se conectará a cada una de las 5 máquinas Redis por turno y ejecutará un script lua para configurar el bloqueo y luego emitir un juicio:

Además, en muchas empresas lógica, no se requiere tiempo de espera de bloqueo.

Por ejemplo, las tareas procesadas en ejecución por lotes en las primeras horas de la mañana pueden requerir bloqueos distribuidos para garantizar que las tareas no se ejecuten repetidamente. En este momento, no está claro cuánto tiempo durará la misión. Establecer el tiempo de espera del bloqueo distribuido aquí tiene poca importancia. Sin embargo, no establecer un tiempo de espera puede provocar problemas de bloqueo.

Entonces, la forma general de resolver este problema es hacer que cada cliente que mantenga el bloqueo inicie un subproceso en segundo plano que ejecutará un script lua específico para actualizar continuamente el tiempo de espera de la clave en Redis para que la clave no se borre. hasta completar la tarea.

El script es el siguiente:

donde ARGV[1] es una variable de parámetro transitable que representa el valor único del sistema que mantiene el bloqueo, lo que significa que solo el cliente que tiene el bloqueo lock La clave expira antes de que pueda actualizarse.

Hasta ahora, se ha implementado un bloqueo distribuido completo. En resumen, el proceso de implementación es el siguiente:

Las cerraduras distribuidas cumplen las siguientes cuatro condiciones:

Por supuesto, para garantizar que se pueda volver a ingresar a la cerradura, tenemos Modificó el script en Redission. El script lua completo ahora se publica de la siguiente manera.

Script Lua para adquirir el bloqueo:

Script correspondiente para actualizar el tiempo de espera del bloqueo:

Script correspondiente para liberar el bloqueo:

Arriba Esto es el escenario de aplicación detallado del uso de Redis como bloqueo distribuido.

En el artículo, presenté los obstáculos durante el uso, así como los detalles de los problemas y las soluciones. Espero que te sea útil.

Finalmente, me gustaría recordarle que el uso del clúster Redis para bloqueos distribuidos es controvertido y requiere que usted tome mejores decisiones y concesiones en función del uso real.

Blogs originales.com/siyuanwai/p/16011836.html