Implementación de Java del ejemplo de código de bloqueo distribuido basado en redis

¿Por qué existe tal demanda?

Por ejemplo, una operación de usuario simple, un hilo para modificar el estado del usuario, primero leer el estado del usuario en la memoria, luego modificarlo en la memoria y luego almacenarlo en la base de datos. En un solo hilo, esto no es un problema. Sin embargo, en el subproceso múltiple, debido a que leer, modificar y escribir son tres operaciones, no operaciones atómicas (éxito o fracaso al mismo tiempo), habrá problemas de seguridad de datos en el subproceso múltiple.

En este caso, puede utilizar bloqueos distribuidos para limitar la ejecución simultánea del programa.

Ideas de realización:

Significa que cuando otros subprocesos entren y operen, se rendirán o intentarán nuevamente más tarde si encuentran que alguien está ocupando un asiento.

Realización de marcador de posición:

Se implementa mediante el comando setnx en redis. Para el comando redis, consulte mi blog https://www.cnblogs.com/javazl/p/12657280.html. El comando set predeterminado es guardar el valor. Cuando el key existe, set is Sobrescribirá el valor de la clave, pero setnx no lo hará. Cuando no hay llave, setnx entrará y ocupará el lugar primero. Cuando la llave exista, otros setnx no podrán entrar. . Espere hasta que se complete la primera ejecución, suelte el asiento en el comando del.

Código:

public class LockTest {
  public static void main(String[] args) {
    Redis redis = new Redis();
    redis.execute(jedis->{
      Long setnx = jedis.setnx("k1", "v1");
     //setnx的返回值为long类型
      if (setnx == 1) {
        //没人占位
        jedis.set("name", "zl");
        String name = jedis.get("name");
        System.out.println(name);
        //释放资源
         jedis.del("k1");
      }else{
        //有人占位,停止/暂缓 操作
      }
    });
  }
}

En el código anterior, es una implementación simple de bloqueo distribuido, pero hay un problema. Es decir, si cuelga antes de soltar después de ocupar. Entonces este hilo nunca se liberará, es decir, no se llama al comando del, todas las solicitudes posteriores se bloquean aquí y el bloqueo se convierte en un punto muerto. Así que aquí hay que optimizarlo.

El método de optimización es agregar un tiempo de vencimiento para garantizar que el bloqueo se pueda liberar después de un cierto período de tiempo.

public class LockTest {
  public static void main(String[] args) {
    Redis redis = new Redis();
    redis.execute(jedis->{
      Long setnx = jedis.setnx("k1", "v1");
      if (setnx == 1) {
        //给锁添加一个过期时间,防止应用在运行过程中抛出异常导致锁无法及时得到释放
        jedis.expire("k1", 5);
        //没人占位
        jedis.set("name", "zl");
        String name = jedis.get("name");
        System.out.println(name);
        jedis.del("k1");
      }else{
        //有人占位,停止/暂缓 操作
      }
    });
  }

Después de este tratamiento, puede asegurarse de que el bloqueo se pueda soltar normalmente. Pero habrá un nuevo problema, es decir, si el servidor cuelga durante la recuperación del bloqueo y la configuración del tiempo de caducidad, porque la recuperación del bloqueo, es decir, setnx y la configuración del tiempo de caducidad son dos operaciones, que no son atómicas y no se pueden completar en el momento. Mismo tiempo. Este bloqueo estará ocupado para siempre, no se puede liberar y se convierte en un punto muerto. Entonces, ¿cómo solucionarlo?

Después de redis2.8, setnx y expireke se pueden ejecutar junto con un comando, y las dos operaciones se vuelven una, lo que resolverá este problema.

Realización optimizada:

public class LockTest {
  public static void main(String[] args) {
    Redis redis = new Redis();
    redis.execute(jedis->{
     //将两个操作合并成一个,nx就是setnx,ex就是expire
      String set = jedis.set("k1", "v1", new SetParams().nx().ex(5));
     //操作结果为okhuo或者error
      if (set !=null && "OK".equals(set)) {
     //给锁添加一个过期时间,防止应用在运行过程中抛出异常导致锁无法及时得到释放
        jedis.expire("k1", 5);
        //没人占位
        jedis.set("name", "zl);
        String name = jedis.get("name");
        System.out.println(name);
      //释放资源
        jedis.del("k1");
      }else{
        //有人占位,停止/暂缓 操作
      }
    });
  }
}

Tras optimizar con el tiempo de caducidad, aunque se soluciona el problema del interbloqueo, surge un nuevo problema, es decir, el problema del timeout:

Por ejemplo: si el negocio a ejecutar requiere mucho tiempo, puede estar desordenado. Cuando un hilo local adquiere el bloqueo, comienza a ejecutar el código comercial, pero el código comercial lleva mucho tiempo. Si el tiempo de vencimiento es 3 segundos, y la ejecución del negocio requiere 5 segundos, de esta manera, el bloqueo se liberará anticipadamente, y luego el segundo subproceso adquiere el bloqueo e inicia la ejecución. Cuando la ejecución llega al segundo segundo, también se ejecuta el primer bloqueo. En este momento, el primer subproceso liberará el bloqueo del segundo subproceso, y luego el tercer subproceso continuará adquiriendo el bloqueo y ejecutándolo. Cuando el tercer segundo se alcanza Después de que se ejecuta el segundo hilo, el bloqueo se liberará por adelantado y el bucle continuará causando caos en el hilo.

Entonces hay dos soluciones principales

Intente evitar operaciones que consumen mucho tiempo.
Para lidiar con el bloqueo, establezca un número aleatorio o una cadena aleatoria al valor del bloqueo y juzgue el valor del valor cada vez que se suelte. Si lo está, suéltelo, si no lo está, no lo liberará. Por ejemplo, supongamos que el primero Cuando entra el hilo, el valor que adquiere el bloqueo es 1. Si se agota el tiempo de espera, entrará en el siguiente hilo y el siguiente hilo adquirirá el nuevo valor como

3. Antes de liberar el segundo lugar, obtenga el valor y compárelo, si 1 no es igual a tres, entonces el bloqueo no se libera.
No hay nada que decir sobre el primer tipo, pero habrá un problema con el segundo tipo, es decir, al liberar el bloqueo se verificará el valor, luego se comparará y luego se liberará. Habrá tres operaciones, por lo que no hay atomicidad Si se realiza esta operación, aparecerá Deadlock. Aquí podemos usar scripts de Lua para procesar.

Características del script Lua:

1. Fácil de usar, redis tiene soporte integrado para scripts Lua.

2.Lua puede ejecutar múltiples comandos redis de forma atómica en el servidor redis

3. Por motivos de la red, el rendimiento de redis se verá afectado, por lo que el uso de Lua puede permitir la ejecución de varios comandos al mismo tiempo, reduciendo los problemas de rendimiento provocados por la red a redis.

Cómo usar los scripts de Lua en redis:

1. Escríbalo en el servidor Redis y luego llame al script en Java Business.

2. Puede escribir directamente en Java. Después de escribirlo, cuando necesite ejecutarlo, envíe el script a redis para su ejecución cada vez.

Cree un script de Lua:

//用redis.call调用一个redis命令,调的是get命令,这个key是从外面传进来的keyif redis.call("get",KEYS[1])==ARGV[1] then//如果相等就去操作释放命令
  return redis.call("del",KEYS[1])
else
 return 0
end

Puede encontrar una suma SHA1 para el script Lua:

cat lua / equal.lua | redis-cli -a root script load --pipe
script load Este comando almacenará en caché el script Lua en Redis y devolverá la suma de comprobación SHA1 del contenido del script, y luego pasará SHA1 cuando se llame en java La suma de comprobación se utiliza como parámetro, para que el servidor de redis sepa qué script ejecutar.

Siguiente escribir en java

public static void main(String[] args) {
    Redis redis = new Redis();
    for (int i = 0; i < 2; i++) {
      redis.execute(jedis -> {
        //1.先获取一个随机字符串
        String value = UUID.randomUUID().toString();
        //2.获取锁
        String k1 = jedis.set("k1", value, new SetParams().nx().ex(5));
        //3.判断是否成功拿到锁
        if (k1 != null && "OK".equals(k1)) {
          //4. 具体的业务操作
          jedis.set("site", "zl");
          String site = jedis.get("site");
          System.out.println(site);
          //5.释放锁
          jedis.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", 
Arrays.asList("k1"), Arrays.asList(value));
        } else {
          System.out.println("没拿到锁");
        }
      });
    }
  }
}

De esta forma se resuelve el problema del interbloqueo.

Algunas preguntas de entrevistas de alta frecuencia recopiladas en el último 2020 (todas organizadas en documentos), hay muchos productos secos, incluidos mysql, netty, spring, thread, spring cloud, jvm, código fuente, algoritmo y otras explicaciones detalladas, así como planes de aprendizaje detallados, entrevistas Clasificación de preguntas, etc. Para aquellos que necesitan obtener estos contenidos, agregue Q como: 11604713672

Supongo que te gusta

Origin blog.csdn.net/weixin_51495453/article/details/113936698
Recomendado
Clasificación