Implementación de base de datos de bloqueo distribuido

Implementación de base de datos de bloqueo distribuido

Que es un candado distribuido

En un sistema de un solo proceso de instancia única, cuando varios subprocesos modifican una variable compartida al mismo tiempo, para garantizar la seguridad de los subprocesos, es necesario sincronizar la variable o el código. Este tipo de operación de sincronización puede utilizar paquetes JUC y sincronizados en Java. Bajo el bloqueo explícito, cas + volátil para lograr.

En la actualidad, la mayoría de los sistemas se implementan de manera distribuida. El uso manual, como el sincronizado, solo puede garantizar la seguridad de los subprocesos dentro de un único proceso. La seguridad de los subprocesos en múltiples procesos y múltiples instancias requiere bloqueos distribuidos.

Actualmente existen cuatro soluciones principales de bloqueo distribuido:

  • Basado en implementación de base de datos (pesimista + optimista)
  • Basado en Redis
  • Basado en ZooKeeper
  • Basado en Etcd

El bloqueo pesimista de la base de datos se da cuenta del bloqueo distribuido

Crear tabla sql:

CREATE TABLE `t_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `amount` int(11) NOT NULL,
  `status` int(11) NOT NULL,
  `version` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

INSERT INTO `t_order` (`amount`, `status`, `version`) VALUES ('100', '1', '1');

Primero use sql para simular la realización de bloqueos distribuidos:

paso SesiónA SesiónB
1 empezar; empezar;
2 seleccione * de t_order donde id = 1 para actualizar;
3 seleccione * de t_order donde id = 1 para actualizar; - 阻塞
4 actualizar t_order set status = 2 donde id = 1 y status = 1;
5 cometer; Devolver el resultado de la consulta
6 update t_order set status = 2 donde id = 1 y status = 1; - el estado ha cambiado pero la actualización no es exitosa
7 cometer;

Descripción:

  1. El cliente A y el cliente B ejecutan las dos primeras filas de sql al mismo tiempo, el cliente A devuelve datos y el cliente B bloquea a la espera de que se adquiera el bloqueo de fila.
  2. El cliente A ejecuta las siguientes dos filas de SQL, confirma la transacción y el cliente B obtiene el bloqueo de fila e inmediatamente devuelve los datos.
  3. El cliente B ejecuta las siguientes dos líneas de SQL, confirma la transacción y libera el bloqueo de fila.

Tenga en cuenta que la for updatedeclaración debe tomar el índice de la clave principal; de lo contrario, toda la tabla se bloqueará si no se toma el índice, y se generarán bloqueos de espacios si se toman otros índices, que pueden bloquear múltiples registros.

Implementación del código Java:

package com.morris.distribute.lock.database.exclusive;

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 org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class OrderService {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 数据库分布式锁之悲观锁
     *
     * @param id
     */
    @Transactional
    public void updateStatus(int id) {
    
    
        log.info("updateStatus begin, {}", id);

        Integer status = jdbcTemplate.queryForObject("select status from t_order where id=? for update", 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=1", 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);
    }
}

Preste atención a la apertura de la transacción @Transactional.

Utilice varios subprocesos para simular bloqueos en competencia:

package com.morris.distribute.lock.database.exclusive;

import com.morris.distribute.config.JdbcConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.stream.IntStream;

public class Demo {
    
    

    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(JdbcConfig.class);
        applicationContext.register(OrderService.class);
        applicationContext.refresh();

        CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
        IntStream.rangeClosed(1, 3).forEach((i) -> new Thread(() -> {
    
    
            OrderService orderService = applicationContext.getBean(OrderService.class);
            try {
    
    
                cyclicBarrier.await();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
    
    
                e.printStackTrace();
            }
            orderService.updateStatus(1);
        }, "t" + i).start());
    }
}

Los resultados son los siguientes:

2020-09-16 14:16:53,248  INFO [t2] (OrderService.java:26) - updateStatus begin, 1
2020-09-16 14:16:53,248  INFO [t1] (OrderService.java:26) - updateStatus begin, 1
2020-09-16 14:16:53,248  INFO [t3] (OrderService.java:26) - updateStatus begin, 1
2020-09-16 14:16:56,289  INFO [t2] (OrderService.java:42) - updateStatus success, 1
2020-09-16 14:16:56,289  INFO [t2] (OrderService.java:49) - updateStatus end, 1
2020-09-16 14:16:56,290  INFO [t3] (OrderService.java:47) - updateStatus status already updated, ignore this request, 1
2020-09-16 14:16:56,290  INFO [t3] (OrderService.java:49) - updateStatus end, 1
2020-09-16 14:16:56,291  INFO [t1] (OrderService.java:47) - updateStatus status already updated, ignore this request, 1
2020-09-16 14:16:56,291  INFO [t1] (OrderService.java:49) - updateStatus end, 1

Se puede ver en los resultados de ejecución que solo un subproceso mantiene el bloqueo al mismo tiempo.

El bloqueo optimista de la base de datos se da cuenta del bloqueo distribuido

El bloqueo optimista utiliza el número de versión para determinar si el registro se ha actualizado cada vez.

package com.morris.distribute.lock.database.share;

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.concurrent.TimeUnit;

@Service
@Slf4j
public class OrderService {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 数据库分布式锁之乐观锁
     *
     * @param id
     */
    public void updateStatus(int id) {
    
    

        log.info("updateStatus begin, {}", id);
        for (;;) {
    
     // 自旋,有可能对订单做其他操作,导致version变了,所以需要自旋
            Order order = jdbcTemplate.queryForObject("select status, version from t_order where id=?",
                    new Object[]{
    
    id}, (rs, row) -> {
    
    
                        Order o = new Order();
                        o.setStatus(rs.getInt(1));
                        o.setVersion(rs.getInt(2));
                        return o;
                    });

            if (Order.ORDER_STATUS_NOT_PAY == order.getStatus()) {
    
    

                try {
    
    
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                int update = jdbcTemplate.update("update t_order set status=?,version=? where id=? and version=? and status=?",
                        new Object[]{
    
    Order.ORDER_STATUS_PAY_SUCCESS, order.getVersion() + 1, id, order.getVersion(), Order.ORDER_STATUS_NOT_PAY});

                if (update > 0) {
    
    
                    log.info("updateStatus success, {}", id);
                    break;
                } else {
    
    
                    log.info("updateStatus failed, {}", id);
                }
            } else {
    
    
                log.info("updateStatus status already updated, ignore this request, {}", id);
                break;
            }
        }
        log.info("updateStatus end, {}", id);
    }

}

Los resultados son los siguientes:

2020-09-16 14:21:08,934  INFO [t3] (OrderService.java:25) - updateStatus begin, 1
2020-09-16 14:21:08,934  INFO [t2] (OrderService.java:25) - updateStatus begin, 1
2020-09-16 14:21:08,934  INFO [t1] (OrderService.java:25) - updateStatus begin, 1
2020-09-16 14:21:12,110  INFO [t1] (OrderService.java:50) - updateStatus failed, 1
2020-09-16 14:21:12,110  INFO [t2] (OrderService.java:50) - updateStatus failed, 1
2020-09-16 14:21:12,111  INFO [t3] (OrderService.java:47) - updateStatus success, 1
2020-09-16 14:21:12,111  INFO [t3] (OrderService.java:57) - updateStatus end, 1
2020-09-16 14:21:12,117  INFO [t2] (OrderService.java:53) - updateStatus status already updated, ignore this request, 1
2020-09-16 14:21:12,117  INFO [t1] (OrderService.java:53) - updateStatus status already updated, ignore this request, 1
2020-09-16 14:21:12,117  INFO [t1] (OrderService.java:57) - updateStatus end, 1
2020-09-16 14:21:12,117  INFO [t2] (OrderService.java:57) - updateStatus end, 1

para resumir

Bloqueo pesimista: bloqueará toda la fila de registros, lo que resultará en la imposibilidad de realizar otras operaciones comerciales en los datos, y es ineficiente. Si el sql no está bien escrito, puede producirse un bloqueo de brechas, bloqueando varios registros o incluso toda la tabla.

Bloqueo optimista: cada tabla debe agregar un campo de versión que no tiene nada que ver con el negocio.

Ventajas: Basado directamente en la implementación de la base de datos, fácil de implementar.

Desventajas: la sobrecarga de E / S es grande, el número de conexiones es limitado y no puede satisfacer las necesidades de alta concurrencia.

Supongo que te gusta

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