Implementación de redis, redisson y escritura a mano de bloqueos distribuidos

Redis de bloqueo distribuido

Redisson

Introducir redission:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.4</version>
</dependency>

La clase de herramienta de bloqueo RLock se ha encapsulado en RedissonClient, y la uso directamente aquí:

package com.morris.distribute.lock.redis.redisson;

import com.morris.distribute.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class OrderService {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 分布式锁之redis(redisson实现)
     *
     * @param id
     */
    public void updateStatus(int id) {
    
    
        log.info("updateStatus begin, {}", id);

        String key =  "updateStatus" + id;

        RLock lock = redissonClient.getLock(key);
        lock.lock(); // 加锁
        try {
    
    
            Integer status = jdbcTemplate.queryForObject("select status from t_order where id=?", new Object[]{
    
    id}, Integer.class);

            if (Order.ORDER_STATUS_NOT_PAY == status) {
    
    

                try {
    
    
                    // 模拟耗时操作
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                int update = jdbcTemplate.update("update t_order set status=? where id=? and status=?", new Object[]{
    
    2, id, Order.ORDER_STATUS_NOT_PAY});

                if (update > 0) {
    
    
                    log.info("updateStatus success, {}", id);
                } else {
    
    
                    log.info("updateStatus failed, {}", id);
                }
            } else {
    
    
                log.info("updateStatus status already updated, ignore this request, {}", id);
            }
            log.info("updateStatus end, {}", id);
        } finally {
    
    
            lock.unlock(); // 释放锁
        }
    }

}

Los resultados son los siguientes:

2020-09-16 14:43:20,778  INFO [main] (Version.java:41) - Redisson 3.12.4
2020-09-16 14:43:21,298  INFO [redisson-netty-2-16] (ConnectionPool.java:167) - 1 connections initialized for 10.0.4.211/10.0.4.211:6379
2020-09-16 14:43:21,300  INFO [redisson-netty-2-19] (ConnectionPool.java:167) - 24 connections initialized for 10.0.4.211/10.0.4.211:6379
2020-09-16 14:43:21,371  INFO [t2] (OrderService.java:29) - updateStatus begin, 1
2020-09-16 14:43:21,371  INFO [t1] (OrderService.java:29) - updateStatus begin, 1
2020-09-16 14:43:21,371  INFO [t3] (OrderService.java:29) - updateStatus begin, 1
2020-09-16 14:43:24,610  INFO [t3] (OrderService.java:51) - updateStatus success, 1
2020-09-16 14:43:24,610  INFO [t3] (OrderService.java:58) - updateStatus end, 1
2020-09-16 14:43:24,620  INFO [t1] (OrderService.java:56) - updateStatus status already updated, ignore this request, 1
2020-09-16 14:43:24,620  INFO [t1] (OrderService.java:58) - updateStatus end, 1
2020-09-16 14:43:24,630  INFO [t2] (OrderService.java:56) - updateStatus status already updated, ignore this request, 1
2020-09-16 14:43:24,630  INFO [t2] (OrderService.java:58) - updateStatus end, 1

Realización de jedis

Implementemos manualmente bloqueos distribuidos de redis a través de jedis, y tengamos una comprensión más profunda del principio de implementación de bloqueos distribuidos de redis.

Presenta jedis:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>

El SET key value NX PX miliseconds:.comando de

¿Qué es un candado exitoso? Quien llame al comando set con la opción nx, la clave no existe, la configuración se realiza correctamente, de lo contrario falla, y quien establezca la clave correctamente obtendrá el bloqueo.

¿Qué debo hacer si el cliente cuelga? Puede establecer un período de tiempo de espera para la llave. Si el cliente cuelga después de que se bloqueó, la llave se eliminará al final del tiempo sin causar un interbloqueo.

¿Qué sucede si la clave está a punto de caducar y la empresa no se ha procesado dentro del período de tiempo de espera? Iniciar un hilo como clave para retrasar.

La implementación específica es la siguiente:

package com.morris.distribute.lock.redis.my;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Slf4j
public class RedisLock {
    
    

    @Autowired
    private JedisPool jedisPool;

    public void lock(String key, String value) {
    
    
        for (; ;) {
    
     // 自旋获取锁
            if (tryLock(key, value)) {
    
    
                return;
            }
            try {
    
    
                TimeUnit.MILLISECONDS.sleep(100); // 这里暂时休眠100ms后再次获取锁,后续可以向AQS一样使用等待队列实现
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    /**
     * 尝试加锁
     *
     * @param key
     * @param value
     * @return
     */
    private boolean tryLock(String key, String value) {
    
    
        SetParams setParams = SetParams.setParams().nx().px(4_000); // 默认超时时间为4s
        Jedis jedis = jedisPool.getResource();
        String result = jedis.set(key, value, setParams);
        if ("OK".equals(result)) {
    
    
            Thread thread = new Thread(() -> {
    
    
                while (true) {
    
    
                    try {
    
    
                        TimeUnit.SECONDS.sleep(1); // 守护线程1s检测一下超时时间
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    Long ttl = jedis.pttl(key);
                    if (ttl < 2_000) {
    
     // 当超时时间小于1/2时,增加超时时间到原来的4s
                        jedis.expire(key, 4_000);
                        log.info("add expire time for key : {}", key);
                    }
                }
            }, "expire1");
            thread.setDaemon(true);
            thread.start();
            return true;
        }
        return false;
    }

    public void unlock(String key, String value) {
    
    
        Jedis jedis = jedisPool.getResource();
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
    }

}

El uso es el siguiente:

package com.morris.distribute.lock.redis.my;

import com.morris.distribute.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class OrderService {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private RedisLock redisLock;

    /**
     * 分布式锁之redis(jedis实现)
     *
     * @param id
     */
    public void updateStatus(int id) {
    
    
        log.info("updateStatus begin, {}", id);

        String key =  "updateStatus" + id;
        String value = UUID.randomUUID().toString();

        redisLock.lock(key, value);
        try {
    
    
            Integer status = jdbcTemplate.queryForObject("select status from t_order where id=?", new Object[]{
    
    id}, Integer.class);

            if (Order.ORDER_STATUS_NOT_PAY == status) {
    
    

                try {
    
    
                    // 模拟耗时操作
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                int update = jdbcTemplate.update("update t_order set status=? where id=? and status=?", new Object[]{
    
    2, id, Order.ORDER_STATUS_NOT_PAY});

                if (update > 0) {
    
    
                    log.info("updateStatus success, {}", id);
                } else {
    
    
                    log.info("updateStatus failed, {}", id);
                }
            } else {
    
    
                log.info("updateStatus status already updated, ignore this request, {}", id);
            }
            log.info("updateStatus end, {}", id);
        } finally {
    
    
            redisLock.unlock(key, value); // 释放锁
        }
    }
}

Los resultados son los siguientes:

2020-09-16 16:19:01,453  INFO [t2] (OrderService.java:28) - updateStatus begin, 1
2020-09-16 16:19:01,453  INFO [t1] (OrderService.java:28) - updateStatus begin, 1
2020-09-16 16:19:01,453  INFO [t3] (OrderService.java:28) - updateStatus begin, 1
2020-09-16 16:19:03,565  INFO [expire1] (RedisLock.java:53) - add expire time for key : updateStatus1
2020-09-16 16:19:04,748  INFO [t1] (OrderService.java:49) - updateStatus success, 1
2020-09-16 16:19:04,749  INFO [t1] (OrderService.java:56) - updateStatus end, 1
2020-09-16 16:19:04,801  INFO [t2] (OrderService.java:54) - updateStatus status already updated, ignore this request, 1
2020-09-16 16:19:04,801  INFO [t2] (OrderService.java:56) - updateStatus end, 1
2020-09-16 16:19:04,902  INFO [t3] (OrderService.java:54) - updateStatus status already updated, ignore this request, 1
2020-09-16 16:19:04,902  INFO [t3] (OrderService.java:56) - updateStatus end, 1

¿Por qué no dos pasos, primero de set key valuenuevo expire key millseconds? Debido a que estas dos operaciones no son operaciones atómicas, si un cliente cuelga después de estar bloqueado, la clave nunca se eliminará, lo que provocará un interbloqueo.

¿Por qué iniciar un hilo de demonio para retrasar la clave? El subproceso del demonio se destruirá automáticamente cuando se cierre el subproceso que lo creó, sin cierre manual. La demora es para permitir que se complete la ejecución de la lógica empresarial y para evitar la caducidad de la clave y permitir que otros subprocesos tomen el bloqueo.

¿Por qué no enviar el del keycomando directamente al liberar el bloqueo ? Al liberar el bloqueo, es necesario verificar el valor para evitar que el bloqueo agregado por el proceso P1 sea liberado por otros procesos. Por lo tanto, la configuración del valor del valor también es exquisita. Solo el proceso P1 conoce este valor, por lo que solo él puede eliminar la llave cuando se libera.

para resumir

Ventajas: Los candados distribuidos basados ​​en redis tendrán las características de redis, es decir, rápidos.

Desventajas: La lógica de implementación es compleja. Redis en sí es un modelo AP, que solo puede garantizar la partición y la disponibilidad de la red, pero no puede garantizar una coherencia sólida. La lógica de bloqueo distribuido es un modelo CP y la coherencia debe garantizarse, por lo que redis es un método de implementación Con cierta probabilidad, varios clientes adquirirán la cerradura. Por ejemplo, el nodo maestro en redis configura correctamente la clave y la devuelve al cliente. En este momento, se cuelga antes de que pueda sincronizarse con el esclavo, y luego el esclavo se elige como el nuevo nodo maestro. Otros clientes lograrán adquirir el bloqueo, de modo que varios clientes puedan adquirir el bloqueo al mismo tiempo.

Supongo que te gusta

Origin blog.csdn.net/u022812849/article/details/108645002
Recomendado
Clasificación