El principio de bloqueo de programación de Java más sólido
Actualmente, existen tres soluciones de implementación principales para bloqueos distribuidos:
La implementación de bloqueos distribuidos basados en bases de datos se logra principalmente mediante el uso del índice único de la base de datos. La exclusividad natural cumple perfectamente con nuestros requisitos para el candado: sólo un competidor puede obtener el candado a la vez. Cuando está bloqueado, insertamos un registro bloqueado en la base de datos y usamos la identificación comercial para evitar la duplicación. Cuando el primer competidor se bloquea con éxito, se genera un conflicto de índice único cuando el segundo competidor se bloquea nuevamente. Si se produce esta excepción, determinamos que el competidor actual no logró bloquear. La identificación comercial antiduplicación debe ser definida por nosotros mismos. Por ejemplo, si nuestro objeto de bloqueo es un método, nuestra ID comercial antiduplicación es el nombre de este método. Si el objeto bloqueado es una clase, el ID comercial antiduplicación es el nombre de la clase.
Implementación de bloqueos distribuidos basados en caché: en teoría, usar caché para implementar bloqueos distribuidos es lo más eficiente y rápido, porque Redis es casi una operación de memoria pura, y las soluciones basadas en bases de datos y las soluciones basadas en Zookeeper involucrar Para el archivo de disco IO, la eficiencia es relativamente baja. Generalmente, el comando de valor clave SETNX de Redis se utiliza para implementar bloqueos distribuidos. El comando tendrá éxito sólo si la clave no existe y fallará si la clave ya existe.
Basado en Zookeeper: Zookeeper se utiliza generalmente como un centro de configuración y su principio de implementación de bloqueos distribuidos es similar a Redis. Creamos nodos transitorios en Zookeeper y utilizamos la función de que los nodos no se pueden crear repetidamente para garantizar la exclusividad.
Al implementar bloqueos distribuidos, debemos considerar algunos problemas, como si los bloqueos distribuidos pueden ser reentrantes, el tiempo de liberación de los bloqueos distribuidos y si hay un problema de un solo punto en el servidor de bloqueos distribuidos.
El principio básico de implementar bloqueos distribuidos basados en la base de datos se ha analizado anteriormente: mantener la exclusividad a través de un índice único, insertar un registro al bloquear y eliminar este registro al desbloquear. Implementemos brevemente un bloqueo distribuido basado en base de datos.
El campo id es el id incrementado automáticamente de la base de datos, y el campo Unique_mutex es nuestro id repetido, que es el objeto bloqueado y es único. Agregamos un índice único a esta tabla para garantizar la unicidad de Unique_mutex. Holder_id representa el ID del titular del candado competidor.
Si el sql actual se ejecuta con éxito, el bloqueo es exitoso; si se lanza una DuplicatedKeyException, el bloqueo falla y el bloqueo actual ha sido adquirido por otros competidores.
Desbloquear es muy sencillo, simplemente borra este registro.
Reentrada: En lo que respecta al esquema anterior, el bloqueo distribuido que implementamos no es reentrante, es decir, si el mismo competidor lo bloquea después de adquirirlo y antes de liberarlo, no podrá ciérrelo para que no vuelva a entrar. Resolver el problema de la no reentrada también es muy simple: al bloquear, determine si hay un registro único_mutex en el registro. Si existe y el titular_id es el mismo que el ID del competidor actual, el bloqueo se realiza correctamente. Esto solucionará el problema de no entrar más.
Tiempo de liberación del bloqueo: imagine que si un competidor adquiere un bloqueo y el proceso se bloquea, este registro en la tabla distribuido_lock siempre existirá y otros competidores no podrán bloquearlo. Para resolver este problema, antes de cada bloqueo, primero determinamos si la diferencia entre la hora de creación del registro existente y la hora actual del sistema ha expirado. Si excede, elimine este registro primero y luego inserte uno nuevo. Además, el propietario de la cerradura debe abrir la cerradura y otros competidores no pueden abrirla. Esto puede ser determinado por el campoholder_id.
Problema de punto único de la base de datos: una base de datos única es propensa a tener problemas de punto único: si la base de datos se bloquea, nuestro servicio de bloqueo también se bloqueará.
Para este problema, podemos considerar soluciones de alta disponibilidad para bases de datos, como la solución de alta disponibilidad MHA de MySQL.
Usa Jedis para comunicarte con Redis.
Como puedes ver, bloqueamos una línea de código:
jedis.set(String key, String value, String nxxx, String expx, int time);
Este método set() tiene cinco parámetros:
El primero es la clave. Usamos llaves como cerraduras porque las llaves son únicas.
El segundo es valor, en el que está escrito el ID del competidor de la cerradura. Al desbloquear, debemos determinar si el ID del competidor actualmente desbloqueado es el titular del candado.
El tercer parámetro es nxxx, completamos NX, que significa SET SI NO EXISTE, es decir, cuando la clave no existe, realizamos la operación SET si la clave ya existe, no se hace nada; .
El cuarto es exPX. Este parámetro es PX, lo que significa que debemos establecer un tiempo de vencimiento para esta clave. El tiempo específico está determinado por el quinto parámetro. uno El parámetro es el tiempo, que corresponde al cuarto parámetro y representa el tiempo de vencimiento de la clave.
En términos generales, ejecutar el método set() anterior solo conducirá a dos resultados: 1. Actualmente no hay ningún bloqueo (la clave no existe), por lo que está bloqueado durante mucho tiempo y se establece un período de validez para el bloqueo. El valor indica el cliente bloqueado. 2. El bloqueo ya existe, así que no hagas nada.
En la solicitud de desbloqueo anterior, SET_IF_NOT_EXIST garantiza la exclusividad de la solicitud de bloqueo, y el mecanismo de tiempo de espera de caché garantiza que incluso si un competidor cuelga después del bloqueo, no habrá problema de punto muerto: otros competidores aún pueden acceder después del tiempo de espera. Al establecer el valor en la identificación del competidor, se garantiza que solo el titular del candado puede desbloquearlo; de lo contrario, cualquier competidor puede desbloquearlo. ¿No es eso un desastre?
Para desbloquear:
Tenga en cuenta que el desbloqueo aquí en realidad se divide en dos pasos, lo que implica un problema de operación atómica de la operación de desbloqueo. Es por eso que usamos el script Lua para desbloquear, porque el script Lua puede garantizar la atomicidad de la operación. Entonces, ¿por qué necesitamos asegurarnos de que estos dos pasos sean operaciones atómicas?
Suponiendo que el poseedor del candado actual es el competidor 1, el competidor 1 se desbloqueará. Si el paso 1 se ejecuta con éxito, se considera que usted es el poseedor del candado. Este es el segundo paso que aún no se ha realizado. Esto se debe a que el candado expiró y luego dos competidores bloquearon la llave. Una vez completado el candado, el competidor 1 vuelve al paso 2 y se produce un error: el competidor 1 desbloquea un candado que no le pertenece. Uno podría preguntarse, ¿por qué el competidor 1 se detuvo repentinamente después de realizar el paso 1? Esta pregunta es realmente fácil de responder. Por ejemplo, la JVM donde se encuentra el competidor 1 tiene una parada de GC, lo que hace que el hilo del competidor 1 se detenga. La probabilidad de que esto suceda es baja, pero tenga en cuenta que incluso una probabilidad entre 10.000 es posible en un entorno de red. Por lo tanto, es necesario garantizar que las operaciones de estos dos pasos sean operaciones atómicas.
Reentrada: El bloqueo anterior no puede ser reentrada. Si es necesario volver a ingresar, después de SET_IF_NOT_EXIST, determine si el valor correspondiente a la clave es la identificación actual del competidor. Si tiene éxito, fracasa.
Tiempo de apertura de cerradura: Configuramos el tiempo de espera de la llave cuando está bloqueada. Después del tiempo de espera, si no se ha desbloqueado, la clave se eliminará automáticamente para lograr el propósito de desbloquear. Si un competidor está bloqueado y cuelga, nuestro servicio de bloqueo no estará disponible como máximo durante el período de tiempo de espera.
Problema de punto único de Redis: si necesita garantizar la alta disponibilidad del servicio de bloqueo, puede crear una solución de alta disponibilidad de Redis: clúster de Redis + conmutación maestro-esclavo. Hay soluciones maduras.
Utilice Zookeeper para crear nodos ordenados temporales para implementar bloqueos distribuidos.
Su idea básica es similar a la cola de espera AQS, que pone en cola las solicitudes para su procesamiento. El diagrama de flujo es el siguiente:
Para resolver el problema de la no reentrada: cuando el cliente se bloquea, escribe la información del host y del subproceso en el bloqueo, y cuando se vuelve a bloquear, la compara directamente con el nodo con la secuencia más pequeña. Si son iguales, el bloqueo se bloquea con éxito y se vuelve a ingresar.
Momento de liberación del bloqueo: dado que el nodo que creamos es un nodo temporal secuencial, cuando el cliente adquiere con éxito el bloqueo y la sesión se desconecta repentinamente, ZK eliminará automáticamente este nodo temporal.
Problema de punto único: ZK se implementa en un clúster si más de la mitad de las máquinas sobreviven, se puede garantizar la disponibilidad del servicio.
Los bloqueos distribuidos basados en Zookeeper se han implementado en el curador de clientes de terceros de Zookeeper. Los códigos para usar el bloqueo y desbloqueo del curador son los siguientes: