Generalmente hay tres formas de implementar cerraduras distribuidas: 1. Bloqueo de base de datos optimista; 2. Bloqueo distribuido basado en Redis; 3. Bloqueo distribuido basado en Zookeeper. Este blog presentará el segundo método, que es implementar el bloqueo distribuido basado en Redis. Aunque hay varios blogs en Internet que introducen la implementación de los bloqueos distribuidos de Redis, su implementación tiene varios problemas. Para evitar niños engañosos, este blog introducirá en detalle cómo implementar correctamente los bloqueos distribuidos de Redis.
fiabilidad
Primero, para garantizar que las cerraduras distribuidas estén disponibles, al menos debemos asegurarnos de que la implementación del bloqueo cumpla con las siguientes cuatro condiciones al mismo tiempo:
Exclusión mutua. En cualquier momento, solo un cliente puede contener el bloqueo.
No se produce un punto muerto. Incluso si un cliente se bloquea durante el período de retención de bloqueo sin desbloquearlo activamente, puede garantizar que otros clientes puedan agregar cerraduras.
Tolerante a fallas. Mientras la mayoría de los nodos redis se ejecuten normalmente, el cliente puede bloquear y desbloquear.
La persona que ató la campana debe ser desatada. El bloqueo y el desbloqueo deben ser el mismo cliente, y el cliente no puede desanimar las cerraduras agregadas por otros.
Implementación del código
Dependencias de componentes
Primero, necesitamos introducir componentes de código abierto de JEDIS a través de Maven y agregar el siguiente código al archivo pom.xml:
<Spendency> <MoupRoD> Redis.Clients </GroupId> <SartifactId> JEDIS </artifactid> <versión> 2.9.0 </versewerency>
Código de bloqueo
Postura correcta
Hablar es barato, muéstrame el código. Primero muestre el código y luego explique por qué se implementa esto:
clase pública redistool {private static final string list_success = "ok"; private static final string set_if_not_exist = "nx"; cadena final de static static staty set_with_expire_time = "px";/*** intente obtener bloqueo distribuido* @param jedis redis cliente* @param bloqueo bloqueo* @param request Id* @Param expiretime expiretime expiretime expiretime se obtiene con éxito*/public static boolean trygetDistributedLock (jEdis jEdis, string LockKey, String requestId, int expireTime) {string dultin = jEdis.set (LockKey, request, set_if_not_exist, set_with_expire_time, expireTime); if (lock_success.egus (resultado)) {return n.Como puede ver, solo agregamos una sola línea de código: jedis.set (StringKey, StringValue, StringNXXX, StringExPX, inttime). Este método set () tiene cinco parámetros formales en total:
La primera es una clave, usamos la tecla como bloqueo, porque la llave es única.
El segundo es el valor. Lo que estamos transmitiendo es requestid. Es posible que muchos zapatos para niños no lo entiendan. ¿No es suficiente tener una llave como bloqueo? ¿Por qué todavía necesitamos usar el valor? La razón es que cuando hablamos sobre la confiabilidad anterior, el bloqueo distribuido debe cumplir con la cuarta condición para descifrar la campana. Al asignar el valor al SolicId, sabremos qué solicitud se agregó al bloqueo, y habrá una base para el desbloqueo. SoldId se puede generar utilizando el método uuid.randomuuid (). toString ().
El tercero es NXXX. Completamos este parámetro nx, que significa setifnotexist, es decir, cuando la clave no existe, realizamos la operación establecida; Si la clave ya existe, no se realiza ninguna operación;
El cuarto es expx. Pasamos este parámetro PX, lo que significa que queremos agregar una configuración caducada a esta clave. El tiempo específico está determinado por el quinto parámetro.
El quinto es el tiempo, que se hace eco del cuarto parámetro y representa el tiempo de vencimiento de la clave.
En general, la ejecución del método set () anterior solo conducirá a dos resultados: 1. No hay bloqueo en la actualidad (la tecla no existe), luego se realiza la operación de bloqueo y el bloqueo se establece para ser válido para el bloqueo, y el valor representa el cliente bloqueado. 2. Ya existe un bloqueo y no se realiza ninguna operación.
Si tiene cuidado, encontrará que nuestro código de bloqueo cumple con las tres condiciones descritas en nuestra confiabilidad. En primer lugar, SET () agrega parámetros NX, lo que puede garantizar que si ya existe una clave, la función no se llamará con éxito, es decir, solo un cliente puede sostener el bloqueo para satisfacer a Mutex. En segundo lugar, dado que establecemos un tiempo de vencimiento para el bloqueo, incluso si el soporte del bloqueo se bloquea en el choque posterior y no se desbloquea, el bloqueo se desbloqueará automáticamente porque ha alcanzado el tiempo de vencimiento (es decir, la llave se elimina), y no habrá un punto muerto. Finalmente, debido a que asignamos valor a SELQUEDID, que representa la identidad de solicitud de cliente bloqueado, entonces el cliente puede verificar si es el mismo cliente al desbloquear. Dado que solo consideramos escenarios de implementación independientes de Redis, no consideraremos la tolerancia a las fallas por el momento.
Ejemplo de error 1
Un ejemplo de error común es usar la combinación de JEDIS.SetNX () y JEDIS.EXPIRE () para lograr el bloqueo. El código es el siguiente:
public static void WrightgetLock1 (JEDIS JEDIS, String LockKey, String SoldId, int ExperTime) {Long result = JEDIS.SetNX (LockKey, requestId); if (result == 1) {// Si el programa se bloquea repentinamente aquí, el tiempo de vencimiento no se puede establecer, y se producirá un punto muerto jedis.ext (LockKey, expirete);}}}}La función del método setnx () es setifnotexist, y el método expire () es agregar un tiempo de vencimiento al bloqueo. A primera vista, parece ser lo mismo que el método de conjunto anterior (). Sin embargo, dado que estos son dos comandos Redis, no son atómicos. Si el programa se bloquea repentinamente después de ejecutar setNX (), el bloqueo no establece el tiempo de vencimiento. Entonces ocurrirá un punto muerto. La razón por la cual algunas personas implementan esto en Internet es que la versión inferior de JEDIS no admite el método de set () set () multiparaméter.
Ejemplo de error 2
Este ejemplo de error es más difícil de encontrar problemas, y la implementación también es más complicada. Idea de implementación: use el comando jedis.setnx () para implementar el bloqueo, donde la tecla es el bloqueo y el valor es el tiempo de vencimiento del bloqueo. Proceso de ejecución: 1. Intente agregar un bloqueo a través del método setnx (). Si el bloqueo actual no existe, el bloqueo se devolverá correctamente. 2. Si el bloqueo ya existe, adquiere el tiempo de vencimiento del bloqueo y compare con la hora actual. Si el bloqueo ha expirado, establezca un nuevo tiempo de vencimiento y devuelva el bloqueo se agrega con éxito. El código es el siguiente:
public static boolean rightgetLock2 (JEDIS JEDIS, String LockKey, int expireTime) {long expires = system.currentTimemillis () + expireTime; string striresstr = string.valueOf (expires); // Si el bloqueo actual no existe, el bloqueo está con éxito si (jedis.setnx (bloqueo de bloqueo, expiresstr) == 1) El bloqueo existe, el tiempo de vencimiento del bloqueo se obtiene cadena CurrentValuestr = jedis.get (LockKey); if (CurrentValuestr! = Null && Long.parselong (currentValuestr) <System.CurrentTimemillis ()) {// El bloqueo ha expirado, obtenga el tiempo de expiración del bloqueo anterior, y establece el tiempo de expiración de la cadena actual de la cadena de bloqueo actual. jedis.getSet (LockKey, ExtiresStr); if (OldValuestr! = Null && OldValuestr.Equals (CurrentValuestr)) {// Considerando el caso de la concurrencia múltiple, solo si el valor de ajuste de un subproceso es el mismo que el valor actual, tiene el derecho a bloquear verdadero;} // en otros casos, la falla de bloqueo será retorno de retorno;}Entonces, ¿cuál es el problema con este código? 1. Dado que el cliente genera el tiempo de vencimiento en sí, es necesario forzar el tiempo de cada cliente a sincronizar bajo el enfoque distribuido. 2. Cuando el bloqueo expira, si varios clientes ejecutan el método jedis.getset () al mismo tiempo, aunque solo un cliente puede bloquear al final, otros clientes pueden sobrescribir el tiempo de vencimiento del bloqueo de este cliente. 3. El bloqueo no tiene el logotipo del propietario, es decir, cualquier cliente puede desbloquearlo.
Desbloquear código
Postura correcta
Primero mostremos el código y luego expliquemos lentamente por qué se implementa esto:
clase pública redistool {private static final long long_success = 1l;/*** Release el bloqueo distribuido* @param jedis redis cliente* @param listeykkey bloqueo* @param requestiD request ID* @return si la versión fue exitosa*/public static static booleanklock (jeDis jedis, string listkey, cadena request) {string script = "" == argv [1] luego regrese redis.call ('del' ', teclas [1]) else return 0 end "; objeto resultado = jedis.eval (script, colección.singletonList (lockKey), colección.singletonList (requestId)); if (ralle_success.equals (resultado)) {return real;} Falso;}}Como puede ver, ¡solo necesitamos dos líneas de código para desbloquearlo! En la primera línea de código, escribimos un código de script Lua simple. La última vez que vimos este lenguaje de programación fue en "Hacker and Painter", pero no esperábamos que se usara esta vez. En la segunda línea de código, pasamos el código LUA al método jedis.eval () y asignamos las claves de parámetros [1] a LockKey y Argv [1] a SoldId. El método eval () es entregar el código LUA al servidor Redis para su ejecución.
Entonces, ¿cuál es la función de este código LUA? De hecho, es muy simple. Primero, obtenga el valor correspondiente al bloqueo, verifique si es igual al Solicid, y si es igual, elimine el bloqueo (desbloqueado). Entonces, ¿por qué usar el lenguaje Lua para implementarlo? Porque es necesario asegurarse de que las operaciones anteriores sean atómicas. Para qué problemas traerán la atomicidad, puede leer [Código de desbloqueo - Ejemplo de error 2]. Entonces, ¿por qué puedo ejecutar el método eval () asegurar la atomicidad, que se origina a partir de las características de Redis? Aquí hay una explicación parcial del comando eval en el sitio web oficial:
En pocas palabras, cuando el comando EVALO ejecute el código LUA, el código LUA se ejecutará como un comando, y Redis no ejecutará otros comandos hasta que se ejecute el comando EVEM.
Ejemplo de error 1
El código de desbloqueo más común es usar directamente el método jedis.del () para eliminar el bloqueo. Este método de desbloquear directamente sin juzgar primero al propietario de la cerradura hará que cualquier cliente se desbloquee en cualquier momento, incluso si el bloqueo no es suyo.
public static void WrightReleaseLock1 (JEDIS JEDIS, String LockKey) {jedis.del (LockKey); }Ejemplo de error 2
A primera vista, este código de desbloqueo está bien. Incluso casi lo implementé así antes, que es similar a la postura correcta. La única diferencia es que se divide en dos comandos para ejecutar. El código es el siguiente:
public static void WrightReleasElock2 (JEDIS JEDIS, String LockKey, String SoldId) {// Determinar si el bloqueo y el desbloqueo son el mismo cliente si (requestiD.equals (jedis.get (bloqueo)))) {// Si este bloqueo no es repentinamente de este cliente, se malinterpretará mal a Jedis.del (LockKey);}}}En cuanto a los comentarios de código, el problema es que si se llama al método jedis.del (), el bloqueo se desbloqueará cuando ya no pertenezca al cliente actual. Entonces, ¿hay realmente un escenario así? La respuesta es sí. Por ejemplo, el cliente se bloquea, y después de un período de tiempo, el cliente se desbloquea. Antes de ejecutar jedis.del (), el bloqueo expira repentinamente. En este momento, el Cliente B trata de bloquear con éxito, y luego el Cliente A ejecuta el método Del (), y luego se desbloquea el bloqueo del Cliente B.
Resumir
Este artículo presenta principalmente cómo implementar correctamente el bloqueo distribuido Redis usando el código Java. Se dan dos ejemplos de errores clásicos para bloquear y desbloquear. De hecho, no es difícil implementar cerraduras distribuidas a través de Redis, siempre y cuando esté garantizado para cumplir con las cuatro condiciones en confiabilidad.
¿En qué escenario se utilizan principalmente cerraduras distribuidas? Cuando se requiere sincronización, por ejemplo, insertar una data requiere verificación de antemano si la base de datos tiene datos similares. Cuando se insertan múltiples solicitudes al mismo tiempo, se puede determinar que la base de datos no tiene datos similares, y se pueden agregar todas ellas. En este momento, se requiere un procesamiento sincrónico, pero la tabla de bloqueo de la base de datos directa requiere mucho tiempo, por lo que se utiliza el bloqueo distribuido Redis. Al mismo tiempo, solo un hilo puede realizar el funcionamiento de la inserción de datos, y otros subprocesos están esperando.
Lo anterior es todo el contenido de este artículo sobre el lenguaje Java que describe la implementación correcta de los bloqueos distribuidos de Redis. Espero que sea útil para todos. Los amigos interesados pueden continuar referiéndose a otros temas relacionados en este sitio. Si hay alguna deficiencia, deje un mensaje para señalarlo. ¡Gracias amigos por su apoyo para este sitio!