Un método de implementación mejor que los bloqueos distribuidos de Redis en escenarios distribuidos a gran escala. Análisis del código fuente del proceso de implementación de bloqueos distribuidos etcd

Veamos la simulación del escenario que se utilizará en este ejemplo: pico de productos básicos, o alta concurrencia, para operaciones de deducción de inventario de productos básicos. Utilizo un pequeño proyecto SpringBoot para simular esta operación.
La pila de tecnología utilizada en este ejemplo:

SpringBoot
Redis,
etcd, la
última colección de 2020 de algunas preguntas de la entrevista (todas organizadas en documentos), hay muchos productos secos, incluidas explicaciones detalladas de netty, primavera, hilo, nube de primavera, etc., también hay planes de aprendizaje detallados, preguntas de entrevista, etc. Siento que La entrevista es muy clara: para obtener la información de la entrevista, simplemente: haga clic aquí para obtenerla !!! Contraseña: CSDNInserte la descripción de la imagen aquí

Antes del código hepático formal, primero comprendamos el mecanismo y el principio de la implementación de bloqueo distribuido de etcd.

El mecanismo básico de la implementación de bloqueo distribuido de etcd

Mecanismo de arrendamiento El mecanismo de
arrendamiento (TTL, Time To Live), etcd puede establecer un arrendamiento para los pares clave-valor almacenados. Cuando el arrendamiento expira, el valor clave se invalidará y eliminará;
al mismo tiempo, también admite la renovación y el arrendamiento expira a través del cliente Renueve el contrato antes
para evitar el vencimiento del par clave-valor.
El mecanismo de Arrendamiento puede garantizar la seguridad de las cerraduras distribuidas y configurar un contrato de arrendamiento para la llave correspondiente a la cerradura.
Incluso si el portador de la cerradura no logra liberar activamente la cerradura debido a una falla, la cerradura se liberará automáticamente cuando el contrato expire.
Mecanismo de revisión
Cada clave tiene un número de revisión, que es +1 para cada transacción. Es globalmente único
. El orden de las operaciones de escritura se puede conocer a través del tamaño de la revisión.
Al implementar bloqueos distribuidos, varios clientes toman los bloqueos al mismo tiempo
y obtienen bloqueos a su vez de acuerdo con el tamaño del número de revisión, lo que puede evitar el "efecto de manada" y lograr bloqueos justos.

Efecto oveja: Las ovejas son una organización muy desorganizada. Por lo general, cuando estamos juntos, corremos ciegamente hacia la izquierda y hacia la derecha. Sin embargo, una vez que una oveja se mueve, otras ovejas se apresuran hacia adelante sin pensar. Lobos y mejor hierba no muy lejos.
El mecanismo de revisión de etcd puede realizar operaciones de escritura de acuerdo con el orden de tamaño del número de revisión, evitando así el "efecto de manada".
Esto es consistente con el principio de que el nodo de secuencia temporal + mecanismo de monitoreo del cuidador del zoológico puede evitar el efecto de manada.

El mecanismo de prefijo
es el mecanismo de prefijo.
Por ejemplo, para un candado llamado / etcd / lock, dos clientes que compiten por él escriben operaciones. Las
claves reales escritas son: key1 = "/ etcd / lock / UUID1", key2 = "/ etcd / lock / UUID2 ".
Entre ellos, UUID representa una identificación única a nivel mundial para garantizar la unicidad de las dos claves.
La operación de escritura será exitosa, pero la Revisión devuelta es diferente,
entonces , ¿cómo determinar quién obtuvo el bloqueo? Consulta a través del prefijo / etcd / lock y devuelve una lista KeyValue que contiene dos pares clave-valor,
así como su Revisión. A través del tamaño de Revisión, el cliente puede determinar si ha adquirido el bloqueo.
El mecanismo de vigilancia
es el mecanismo de seguimiento.
El mecanismo Watch admite una tecla fija de Watch y una gama de Watch (mecanismo de prefijo).
Cuando la llave vigilada o el rango cambia, el cliente recibirá una notificación; al implementar cerraduras distribuidas, si falla la cerradura,
la lista de Valor-Clave devuelta por el mecanismo de Prefijo se puede utilizar para obtener la llave cuya Revisión es menor y la menor diferencia ( Llamada pre-llave),
monitorea la pre-llave, porque solo libera la cerradura, puede obtener la cerradura, si mira el evento DELETE de
la pre-llave , significa que la pre-llave ha sido liberada y mantendrá la cerradura.

etcd diagrama de principio de bloqueo distribuido

Inserte la descripción de la imagen aquí

Proceso de implementación del bloqueo distribuido etcd

establecer conexión

El cliente se conecta a etcd y crea una clave única global con el prefijo / etcd / lock.
Suponga que el primer cliente corresponde a key = "/ etcd / lock / UUID1", y el segundo es key = "/ etcd / lock / UUID2 "; El
cliente crea un arrendamiento para su propia clave respectivamente-Arrendamiento, la duración del arrendamiento se determina de acuerdo con el tiempo comercial;

Cree una tarea programada como el "latido" de la concesión

Mientras un cliente mantiene el candado, otros clientes solo pueden esperar. Para evitar el vencimiento del arrendamiento durante el período de espera, el
cliente debe crear una tarea cronometrada como un "latido" para la renovación. Además, si el cliente se bloquea mientras mantiene presionado el candado y el
latido se detiene, la clave se eliminará debido al vencimiento del contrato de arrendamiento, de modo que el candado se libera y se evita el interbloqueo;

El cliente escribe su clave única global en etcd

Ejecute la operación put y escriba la concesión de vinculación de claves creada en el paso 1 en Etcd. De acuerdo con el mecanismo de revisión de Etcd,
asumiendo que las revisiones devueltas por las dos operaciones put del cliente son 1, 2 respectivamente, el cliente debe registrar la revisión para el
siguiente juicio Ya sea para obtener el candado;

El cliente juzga si debe obtener el candado

El cliente lee la lista de clave-valor con el prefijo / etcd / lock / y juzga si la revisión de su clave es la
más pequeña en la lista actual, y si lo es, se considera que está bloqueada; de lo contrario, monitorea la eliminación de la revisión anterior en la lista con una clave más pequeña Evento, una vez que se monitorea un evento de eliminación o un evento eliminado debido al vencimiento del arrendamiento, el bloqueo se adquirirá por sí mismo;

Realizar negocios

Después de obtener el bloqueo, opere el recurso compartido y ejecute el código comercial

Liberar bloqueo

Después de completar el proceso comercial, elimine la clave correspondiente para liberar el bloqueo

Código

Con la teoría anterior como base, comenzamos la implementación del código del bloqueo distribuido etcd.

cliente jetcd

jetcd es un cliente Java de etcd, que proporciona una rica interfaz para operar etcd, que es fácil de usar.

Inserte la descripción de la imagen aquí

Preparación de datos de Redis

Inicialice el stock de inventario = 300, y luego establezca un lucky = 0, lo que significa que la persona que toma el inventario puede ser la información del pedido del usuario en la escena real. Por cada deducción de un inventario, la suerte aumentará en 1.Inserte la descripción de la imagen aquí

Implementación de bloqueo distribuido etcd

Dado que la interfaz de bloqueo de etcd tiene su propio conjunto de implementaciones, y la interfaz de bloqueo de zookeeper también tiene su propio conjunto de implementaciones, redis ... varios esquemas de implementación de bloqueo distribuido tienen sus propios bloqueos, así que encapsulé un método de plantilla:

/**
 * @program: distributed-lock
 * @description: 各种分布式锁的基类,模板方法
 * @author: 行百里者
 * @create: 2020/10/14 12:29
 **/
public class AbstractLock implements Lock {
    
    
    @Override
    public void lock() {
    
    
        throw new RuntimeException("请自行实现该方法");
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    
    
        throw new RuntimeException("请自行实现该方法");
    }

    @Override
    public boolean tryLock() {
    
    
        throw new RuntimeException("请自行实现该方法");
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    
    
        throw new RuntimeException("请自行实现该方法");
    }

    @Override
    public void unlock() {
    
    
        throw new RuntimeException("请自行实现该方法");
    }

    @Override
    public Condition newCondition() {
    
    
        throw new RuntimeException("请自行实现该方法");
    }
}

Con este método de plantilla, las implementaciones posteriores de bloqueos distribuidos pueden heredar esta clase de método de plantilla.

Implementación de bloqueo distribuido etcd:

@Data
public class EtcdDistributedLock extends AbstractLock {
    
    
    private final static Logger LOGGER = LoggerFactory.getLogger(EtcdDistributedLock.class);

    private Client client;
    private Lock lockClient;
    private Lease leaseClient;
    private String lockKey;
    //锁路径,方便记录日志
    private String lockPath;
    //锁的次数
    private AtomicInteger lockCount;
    //租约有效期。作用 1:客户端崩溃,租约到期后自动释放锁,防止死锁 2:正常执行自动进行续租
    private Long leaseTTL;
    //续约锁租期的定时任务,初次启动延迟,默认为1s,根据实际业务需要设置
    private Long initialDelay = 0L;
    //定时任务线程池
    ScheduledExecutorService scheduledExecutorService;
    //线程与锁对象的映射
    private final ConcurrentMap<Thread, LockData> threadData = Maps.newConcurrentMap();

    public EtcdDistributedLock(Client client, String lockKey, Long leaseTTL, TimeUnit unit) {
    
    
        this.client = client;
        this.lockClient = client.getLockClient();
        this.leaseClient = client.getLeaseClient();
        this.lockKey = lockKey;
        this.leaseTTL = unit.toNanos(leaseTTL);
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    }

    @Override
    public void lock() {
    
    
        
    }

    @Override
    public void unlock() {
    
    
        
    }
}

La realización del método de bloqueo:

@Override
public void lock() {
    
    
    Thread currentThread = Thread.currentThread();
    LockData existsLockData = threadData.get(currentThread);
    //System.out.println(currentThread.getName() + " 加锁 existsLockData:" + existsLockData);
    //锁重入
    if (existsLockData != null && existsLockData.isLockSuccess()) {
    
    
        int lockCount = existsLockData.lockCount.incrementAndGet();
        if (lockCount < 0) {
    
    
            throw new Error("超出etcd锁可重入次数限制");
        }
        return;
    }
    //创建租约,记录租约id
    long leaseId;
    try {
    
    
        leaseId = leaseClient.grant(TimeUnit.NANOSECONDS.toSeconds(leaseTTL)).get().getID();
        //续租心跳周期
        long period = leaseTTL - leaseTTL / 5;
        //启动定时续约
        scheduledExecutorService.scheduleAtFixedRate(new KeepAliveTask(leaseClient, leaseId),
                initialDelay,
                period,
                TimeUnit.NANOSECONDS);

        //加锁
        LockResponse lockResponse = lockClient.lock(ByteSequence.from(lockKey.getBytes()), leaseId).get();
        if (lockResponse != null) {
    
    
            lockPath = lockResponse.getKey().toString(StandardCharsets.UTF_8);
            LOGGER.info("线程:{} 加锁成功,锁路径:{}", currentThread.getName(), lockPath);
        }

        //加锁成功,设置锁对象
        LockData lockData = new LockData(lockKey, currentThread);
        lockData.setLeaseId(leaseId);
        lockData.setService(scheduledExecutorService);
        threadData.put(currentThread, lockData);
        lockData.setLockSuccess(true);
    } catch (InterruptedException | ExecutionException e) {
    
    
        e.printStackTrace();
    }
}

En resumen, el código de bloqueo se basa en los siguientes pasos:

Verificar la reentrada de la cerradura
Establecer el arrendamiento
Abrir la tarea de temporización verificación del latido
Bloquear la adquisición del bloqueo Bloquear
correctamente, establecer el objeto de bloqueo

Una vez completado el procesamiento comercial (deducción de inventario), desbloquee:

@Override
public void unlock() {
    
    
    Thread currentThread = Thread.currentThread();
    //System.out.println(currentThread.getName() + " 释放锁..");
    LockData lockData = threadData.get(currentThread);
    //System.out.println(currentThread.getName() + " lockData " + lockData);
    if (lockData == null) {
    
    
        throw new IllegalMonitorStateException("线程:" + currentThread.getName() + " 没有获得锁,lockKey:" + lockKey);
    }
    int lockCount = lockData.lockCount.decrementAndGet();
    if (lockCount > 0) {
    
    
        return;
    }
    if (lockCount < 0) {
    
    
        throw new IllegalMonitorStateException("线程:" + currentThread.getName() + " 锁次数为负数,lockKey:" + lockKey);
    }
    try {
    
    
        //正常释放锁
        if (lockPath != null) {
    
    
            lockClient.unlock(ByteSequence.from(lockPath.getBytes())).get();
        }
        //关闭续约的定时任务
        lockData.getService().shutdown();
        //删除租约
        if (lockData.getLeaseId() != 0L) {
    
    
            leaseClient.revoke(lockData.getLeaseId());
        }
    } catch (InterruptedException | ExecutionException e) {
    
    
        //e.printStackTrace();
        LOGGER.error("线程:" + currentThread.getName() + "解锁失败。", e);
    } finally {
    
    
        //移除当前线程资源
        threadData.remove(currentThread);
    }
    LOGGER.info("线程:{} 释放锁", currentThread.getName());
}

Proceso de desbloqueo:

Comprobación de reentrada
Elimina la ruta del nodo del bloqueo actual Libera el bloqueo Elimina los
recursos del subproceso reentrante

Prueba de interfaz

/**
 * @program: distributed-lock
 * @description: etcd分布式锁演示-高并发下库存扣减
 * @author: 行百里者
 * @create: 2020/10/15 13:24
 **/
@RestController
public class StockController {
    
    

    private final StringRedisTemplate redisTemplate;

    @Value("${server.port}")
    private String port;

    @Value("${etcd.lockPath}")
    private String lockKey;

    private final Client etcdClient;

    public StockController(StringRedisTemplate redisTemplate, @Value("${etcd.servers}") String servers) {
    
    
        //System.out.println("etcd servers:" + servers);
        this.redisTemplate = redisTemplate;
        this.etcdClient = Client.builder().endpoints(servers.split(",")).build();
    }

    @RequestMapping("/stock/reduce")
    public String reduceStock() {
    
    
        Lock lock = new EtcdDistributedLock(etcdClient, lockKey, 30L, TimeUnit.SECONDS);
        //获得锁
        lock.lock();
        //扣减库存
        int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
    
    
            int realStock = stock - 1;
            redisTemplate.opsForValue().set("stock", String.valueOf(realStock));
            //同时lucky+1
            redisTemplate.opsForValue().increment("lucky");
        } else {
    
    
            System.out.println("库存不足");
        }
        //释放锁
        lock.unlock();
        return port + " reduce stock end!";
    }
}

Esto es muy simple. Cuando ingrese una solicitud, primero intente bloquearla. Después de que el bloqueo sea exitoso, ejecute el negocio y deduzca el inventario. Al mismo tiempo, la información del pedido es +1. Una vez que se completa el procesamiento comercial, el bloqueo se libera.
Stress test Se
completó la interfaz de prueba Utilice JMeter para simular un escenario de alta concurrencia, envíe 500 solicitudes al mismo tiempo (el inventario es solo 300) y observe los resultados.
Inicie dos servicios primero, uno 8080 y otro 8090:

Inserte la descripción de la imagen aquí
Configurar nginx (principalmente para facilitar la simulación de alta concurrencia y distribución): Inserte la descripción de la imagen aquí
La dirección IP de nginx es 192.168.2.10: Inserte la descripción de la imagen aquí
Por lo tanto, para nuestra prueba de esfuerzo, solo necesitamos enviar una solicitud a la interfaz http://192.168.2.10/stock/reduce.

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Resultados de la prueba de presión de ejecución: ¡Los Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
resultados muestran que nuestro bloqueo distribuido etcd es exitoso!

para resumir

Algunas preguntas de la entrevista recopiladas en el último 2020 (todas organizadas en documentos), hay muchos productos secos, incluidas explicaciones detalladas de netty, primavera, hilo, nube de primavera, etc., también hay planes de aprendizaje detallados, preguntas de la entrevista, etc. Siento que estoy en la entrevista. Hablando muy claro: para obtener la información de la entrevista, simplemente: haga clic aquí para obtenerla !!! Contraseña: CSDN
Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/a3961401/article/details/109249069
Recomendado
Clasificación