Ruta de aprendizaje de Redis (6) - Bloqueo distribuido de Redis

1. Modelo de bloqueo distribuido

(1) Bloqueo pesimista: se cree que los problemas de seguridad de subprocesos definitivamente ocurrirán, por lo tanto, adquiera el bloqueo antes de operar los datos para asegurarse de que los subprocesos se ejecuten en serie. Por ejemplo, Sincronizado y Bloqueo son bloqueos pesimistas.

  • Ventajas: simple y grosero.
  • Desventajas: rendimiento ligeramente inferior

(2) Bloqueo optimista: se cree que los problemas de seguridad de subprocesos no necesariamente pueden ocurrir, por lo que no se agrega ningún bloqueo. Solo cuando se actualizan los datos se puede juzgar si otros subprocesos han modificado los datos. Si no hay modificación, se considera seguro , y los datos pueden actualizarse por sí mismos; si han sido modificados por otros subprocesos, significa que ha ocurrido un problema de seguridad y puede volver a intentarlo o hacer una excepción en este momento.

  • Ventajas: buen rendimiento
  • Desventajas: hay un problema de baja tasa de éxito

(3) Métodos de implementación comunes:

  • Método del número de versión: a través de la estructura id-stock-version, juzgue si se ha modificado comprobando si la versión es la misma que esta vez.
  • Método CAS: Es una versión mejorada del método del número de versión.Utiliza la estructura de viejo-consulta-nuevo.Si el stock obtenido a través de la primera consulta es consistente con el stock enviado por segunda vez, si son consistentes, viejo = nuevo;

2. Bloqueo distribuido de Redis

(1) La función de los bloqueos distribuidos: como monitor de bloqueo de JVM común, cada JVM en el clúster puede obtener los subprocesos monitoreados por el monitor de bloqueo, y varias JVM sincronizan la ejecución de subprocesos internamente.

(2) Requisitos para bloqueos distribuidos: visibilidad multiproceso, exclusión mutua, alta disponibilidad, alto rendimiento, seguridad...

(3) Diferencias de bloqueos distribuidos comunes:

mysql redis cuidador del zoológico
mutuamente excluyentes Use el mecanismo de bloqueo mutex de mysql Use comandos mutex como setnx Utilice la unicidad y el orden de los nodos para lograr la exclusión mutua
alta disponibilidad bien bien bien
alto rendimiento generalmente bien generalmente
seguridad Desconecte, libere automáticamente la cerradura Use el tiempo de espera de bloqueo para liberar cuando expire Nodos temporales, desconectados y liberados automáticamente

(4) Redis implementa bloqueos distribuidos

1. Consigue el candado:

  • Exclusión mutua: asegura que solo un hilo adquiera el bloqueo.SETNX lock thread1

2. Suelta el candado

  • Liberar manualmente: DEL lock
  • Lanzamiento caducado: EXPIRE lock 5

(1) Realice la operación atómica a través de la operación SET: SET lock thread1 EX 10 NX , lo que significa crear un caché de bloqueo, el valor es thread1 y mantenerlo durante 10 segundos, y NX es una operación de exclusión mutua

(2) El método cuando falla la adquisición de bloqueo:

  • Adquisición de bloqueo de bloqueo: espere hasta que un subproceso libere el bloqueo. (alto consumo de recursos de la CPU)
  • Adquisición de bloqueo sin bloqueo: si falla, no se realizarán más intentos de adquirir el bloqueo.

3. El código realiza el bloqueo distribuido.

(1) Requisitos: defina una clase para implementar la función de bloqueo distribuido de Redis.

public class SimpleRedisLock implements ILockService {
    
    

    private static final String LOCK = "lock:";

    private String threadName;

    private StringRedisTemplate redisTemplate;

    public SimpleRedisLock() {
    
    
    }

    public SimpleRedisLock(String threadName, StringRedisTemplate redisTemplate) {
    
    
        this.threadName = threadName;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
    
    
        //1、获取锁
        Boolean absent = redisTemplate.opsForValue().setIfAbsent(LOCK + threadName, String.valueOf(Thread.currentThread().getId()), timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(absent);
    }

    @Override
    public void unlock() {
    
    
    	//2、解锁
        redisTemplate.delete(LOCK + threadName);
    }
}

4. Optimización de bloqueo distribuido basada en Redis - Objeto Redisson

(1) Problemas con bloqueos distribuidos basados ​​en setnx

  • No reentrante: el mismo subproceso no puede adquirir el mismo bloqueo varias veces
  • No reintentable: se devuelve falso después de un solo intento de adquirir el bloqueo, no hay mecanismo de reintento
  • Liberación de bloqueo de tiempo de espera: liberación de bloqueo de tiempo de espera, aunque se puede evitar el punto muerto, pero si el negocio tarda demasiado, también hará que se libere el bloqueo, lo que representa un riesgo de seguridad.
  • Coherencia maestro-esclavo: si Redis proporciona un clúster maestro-esclavo, hay un retraso en la sincronización maestro-esclavo.Cuando el maestro está inactivo, si el esclavo sincroniza todos los datos en el maestro, se producirá la implementación del bloqueo.

(2) Un objeto común para implementar bloqueos distribuidos - Redisson

1. Concepto: Redisson es una red de datos en memoria de Java (In-Memory Data Grid) implementada sobre la base de Redis. Proporciona una serie de objetos Java comunes distribuidos y también proporciona muchos servicios distribuidos, incluida la implementación de varios bloqueos distribuidos.

2. Tipos de bloqueos distribuidos Dirección del sitio web oficial: https://redisson.org

  • Bloqueo distribuido (Lock) y sincronizador (Synchronizer)
    • Bloqueo reentrante
    • Bloqueo justo (bloqueo justo)
    • Enclavamiento (bloqueo múltiple)
    • candado rojo
    • Bloqueo de lectura y escritura (Bloqueo de lectura y escritura)
    • Semáforo
    • Semáforo expirable (PermitExpirableSemaphore)
    • Pestillo (CountDownLatch)

3. Uso básico de Redisson

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.13.6</version>
</dependency>
@Configuration
public class RedisConfig {
    
    
	@Bean
	public RedissonClient redissonClient() {
    
    
		//配置类
		Config config = new Config();
		//连接redis
		config.useSingleServer().setAddress("redis://192.168.92.131:6379").setPassword("123321");
		//创建客户端
		return Redisson.create(config);
	}
}
@Resource
private RedissonClient redissonClient;

@Test
void testRedisson() throws InterruptedException {
    
    
	//	获取锁(可重入),指定锁名称
	RLock lock = redissonClient.getLock("anyLock");
	//	尝试获取锁,参数分别是: 获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
	boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
	//	判断释放获取成功
	if(isLock){
    
    
		try{
    
    
			System.out.println("执行业务");
		}finally{
    
    
			//释放锁
			lock.unlock();
		}
	}
}

4. Principio de bloqueo de reentrada de Redisson

(1) ¿Qué es una cerradura reentrante?

Un bloqueo reentrante se refiere a un mecanismo de adquisición de bloqueo en el que un subproceso puede adquirir un bloqueo varias veces.

(2) Principio de reentrada ReentrantLock

Cuando un subproceso adquiere un bloqueo, si hay un subproceso ocupando el bloqueo, verifique si el subproceso es él mismo. Si es él mismo, intente adquirir el bloqueo nuevamente. Cada vez que intente adquirir el bloqueo, habrá un contador de reingreso para registrar el número de reentradas de subprocesos, después de que finaliza la ejecución de este método, se libera el bloqueo y el contador de reentradas se reduce correspondientemente en uno, hasta que se ejecuta todo el método, el bloqueo se puede liberar por completo.

(3) Implementación de bloqueos de reentrada basados ​​en Redis

Proceso: determine si existe el bloqueo (si no, adquiera el bloqueo y agregue la ID del subproceso, establezca el período de validez del bloqueo, ejecute el negocio y todavía necesita determinar la propiedad del bloqueo y el estado del conteo de bloqueos después de la ejecución) 》Juez si el ID de bloqueo es suyo (si es así, entonces el bloqueo cuenta + 1) "Si no, la adquisición del bloqueo falló"

guion lua

local key = KEYS[1];
local threadId = ARGV[1];
local releaseTime = ARGV[2];

--1、判断当前锁是否是自己
if (redis.call("HEXISTS", key, threadId) == 0) then
    -- 不是,则直接返回
    return nil;
end
-- 如果是,则计数器-1
local count = redis.call("HINCRBY", key, threadId, -1);

--2、判断统计数是否为0
if (count > 0) then
    -- 统计数不为零,则重置计时器
    redis.call("expire", key, releaseTime);
else
    --统计数为零,则释放锁
    redis.call("del", key);
    return nil;
end

5. multiLock, consistencia maestro-esclavo

(1) Causa: varios Redis, el nodo maestro almacena principalmente los datos más recientes y los nodos esclavos necesitan sincronizar datos.Durante el proceso de sincronización de datos, habrá un retraso, porque algunas anormalidades hacen que el nodo maestro se caiga, y los nodos esclavos Los datos son inconsistentes y debido a que el redis bloqueado por el objeto de bloqueo está inactivo, el bloqueo se vuelve inválido, causando todos los problemas de seguridad distribuida mencionados anteriormente.

(2) Solución: mecanismo de enclavamiento.

(3) Solución a la coherencia maestro-esclavo del mecanismo de enclavamiento: al adquirir todos los bloqueos del clúster de Redis, solo cuando se adquieren todos los bloqueos del clúster de Redis se puede realizar la actualización de datos.

(4) interbloqueo Redssion: MultiLock

6. Reintento de bloqueo de Redisson y mecanismo WatchDog

(1) El proceso básico del mecanismo de reintento de bloqueo de Redisson: puede verificar el mecanismo de reintento de bloqueo y liberar el mecanismo de bloqueo de la clase RedissonLock usted mismo

  • Convierta el tiempo de espera y el tiempo de liberación al nivel de milisegundos de manera uniforme
  • El primer intento de adquirir el bloqueo (si el TTL devuelto es nulo, significa que se ha adquirido el bloqueo)
    • Determinar si adquirir un bloqueo (bloquear y esperar a que regrese el resultado)
      • Determine si el tiempo de liberación de bloqueo es el predeterminado (se pasa el parámetro -1 para indicar el predeterminado), si es el predeterminado, use WatchDog para monitorear el tiempo (30 s), de lo contrario prevalecerá el parámetro pasado
      • Use una función de liberación asíncrona, que contiene un script para la implementación de bloqueo de reentrada (el resultado se devuelve a una clase Futuro)
  • Si no se adquiere el bloqueo, el tiempo de espera se resta del tiempo de adquisición del bloqueo, y se juzga si el tiempo de espera es insuficiente (si no, se rinde y se vuelve a intentar y se devuelve un mensaje de error)
  • Suscríbase al ID de subproceso que actualmente intenta adquirir el bloqueo (bloqueando y esperando el tiempo de espera restante, si todavía no hay señal para liberar el bloqueo, cancele la suscripción y devuelva un mensaje de error), cuando el comando en el script de liberación de bloqueo se ejecuta, la adquisición de bloqueo publishcomenzará
  • Suscríbete a la señal de bloqueo
    • Juzgar si el tiempo de espera es suficiente (primero se resta el tiempo de espera del tiempo de la señal de espera, si el resultado del cálculo es menor que cero, se devolverá un mensaje de error)
    • Si es suficiente, intentará adquirir el bloqueo en un bucle hasta que el bloqueo se adquiera con éxito, o se devolverá un error si el tiempo de espera es insuficiente (el intento de bucle no se repite todo el tiempo, solo intentará adquirir cuando se adquiere la señal de bloqueo)

inserte la descripción de la imagen aquí


3. Problemas relacionados con el uso de bloqueos distribuidos

(1) Modificación excesiva de datos

1. Razones: debido a la participación de subprocesos múltiples, el orden de ejecución de los métodos de los módulos funcionales será diferente, por lo que cuando varios subprocesos pueden haber consultado sobre el inventario debido al orden de operación diferente y todavía hay un excedente, todos realizarán la operación de deducir el inventario, de modo que el número de inventario original < el número de subprocesos de solicitud, lo que da como resultado una situación en la que el inventario se vuelve negativo directamente.

2. Solución: Añadir un candado para mantener el candado en poder del usuario al acceder a modificar la operación.

3. Agregue bloqueo optimista y bloqueo pesimista para resolver el problema

(1) Bloqueo optimista: dado que los cambios de datos antes y después de la actualización se utilizan para juzgar si los datos se pueden actualizar, se accede a los subprocesos al mismo tiempo, después de que el subproceso a accede a la actualización, el subproceso b accede a los datos antes y después de la actualización es inconsistente, lo que hace que la actualización del subproceso falle.Al mismo tiempo, todos los subprocesos a los que se accedió durante el mismo período fallan, por lo que su eficiencia será extremadamente baja, pero el negocio aún puede completarse.

(2) Agregar bloqueo pesimista: el subproceso a adquirió el bloqueo y entró en la fase de actualización de acceso, pero el subproceso b no adquirió el bloqueo y está bloqueado. Si no hay un mecanismo de reintento de bloqueo, una gran cantidad de subprocesos pueden fallar. En comparación con bloqueo optimista De esta manera, la tasa de éxito obviamente ha mejorado, y su seguridad también ha mejorado, y no se actualizará varias veces debido a múltiples solicitudes idénticas del mismo usuario.

(2) En el estado del clúster, la función de bloqueo falla

1. Motivos: JVM mantiene un monitor de bloqueo interno. Bajo el mismo ID de usuario, este subproceso se considera el mismo subproceso, pero cuando salen dos o más clústeres de JVM, el monitor de bloqueo no bloquea el mismo subproceso. serán problemas de seguridad de concurrencia.

2. Solución: usar bloqueos distribuidos

(3) El bloqueo del servicio conduce a la liberación del tiempo de espera de bloqueo

1. Motivo: el subproceso está bloqueado y el bloqueo distribuido se libera después del tiempo de espera, lo que hace que el subproceso se ejecute de forma caótica.

2. Solución: después de completar el negocio, primero verifique si la identificación del candado es consistente y luego juzgue si liberar el candado.

(4) Bloqueo de liberación de tiempo de espera

1. Causa: debido al mecanismo de recolección de basura de la JVM, el subproceso puede bloquearse antes de liberar el bloqueo, lo que hace que el bloqueo se libere con el tiempo

2. Solución: hacer la representación del juicio y liberar el bloqueo atómico.

3. Método de implementación: use secuencias de comandos Lua para escribir múltiples Redis para garantizar la atomicidad de los comandos de Redis.

3. Cómo usar los scripts de Lua: Redis proporciona una función de devolución de llamada que puede llamar a los scripts.

EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name Rose

4. El proceso comercial de liberar el bloqueo

  • Obtener la identificación del hilo en la cerradura
  • Determinar si es consistente con la ID especificada (ID de subproceso actual)
  • Liberar bloqueo si es consistente (eliminar)
  • no hacer nada si es inconsistente

Supongo que te gusta

Origin blog.csdn.net/Zain_horse/article/details/132002653
Recomendado
Clasificación