SpringCloud integra RocketMQ para lograr la consistencia final de los mensajes confiables [tabla de mensajes locales, esquema de mensajes de transacciones de RocketMQ]

Tabla de contenido

1. Una descripción general de la coherencia final de los mensajes fiables

En segundo lugar, la solución [solución de tabla de mensajes local]

En tercer lugar, la solución [programa de mensajes de transacciones de RocketMQ]

Cuatro, RocketMQ realiza una transacción consistente final de mensaje confiable

Cinco, resumen


1. Una descripción general de la coherencia final de los mensajes fiables


El esquema de coherencia eventual de mensajes confiable significa que cuando el iniciador de la transacción completa la transacción local y envía un mensaje, el participante de la transacción (consumidor del mensaje) debe poder recibir el mensaje y procesar la transacción con éxito. Este esquema enfatiza que siempre que el mensaje se envíe al participante de la transacción Los asuntos finales de las partes deben ser coherentes . Este programa se completa mediante el uso de middleware de mensajes, como se muestra en la siguiente figura:

El iniciador de la transacción (productor del mensaje) envía el mensaje al middleware del mensaje, el participante de la transacción recibe el mensaje del middleware del mensaje, entre el iniciador de la transacción y el middleware del mensaje, y entre el participante de la transacción (consumidor del mensaje) y el middleware del mensaje. Toda la comunicación se realiza a través de la red y la incertidumbre de la comunicación en la red provocará problemas de transacciones distribuidas. Por lo tanto, la eventual consistencia de mensajes confiables debe resolver los siguientes problemas:
[1] La atomicidad de las transacciones locales y el envío de mensajes : el iniciador de la transacción debe enviar el mensaje después de que la transacción local se haya ejecutado con éxito, de lo contrario, el mensaje se descarta. Es decir, para lograr la atomicidad de las transacciones locales y el envío de mensajes , ambos tienen éxito o ambos fallan. La atomicidad de las transacciones locales y el envío de mensajes es la cuestión clave para lograr la consistencia final de mensajes confiables . Probemos este tipo de operación primero, enviemos el mensaje primero y luego operemos la base de datos : en este caso, no se puede garantizar la consistencia de la operación de la base de datos y el mensaje enviado, porque el mensaje puede enviarse con éxito y la operación de la base de datos falló.

begin transaction;

//1.发送MQ

//2.数据库操作

commit transation;

La segunda solución es realizar primero las operaciones de la base de datos y luego enviar los mensajes : en este caso, parece que no hay ningún problema. Si el mensaje MQ no se envía, se lanzará una excepción, lo que provocará que la transacción de la base de datos retroceda. pero:

  • Si se trata de una excepción de tiempo de espera , la base de datos se retrotrae, pero el MQ se ha enviado con normalidad, lo que también provocará inconsistencias.
  • Si el mensaje se envía con éxito, la JVM se bloquea repentinamente cuando la transacción finalmente se confirma y la transacción no se confirma correctamente, lo que da como resultado datos inconsistentes entre los dos sistemas.
begin transaction;

//1.数据库操作

//2.发送MQ

commit transation;

[2] Fiabilidad del mensaje recibido por los participantes de la transacción : Los participantes de la transacción deben poder recibir mensajes de la cola de mensajes. Si la recepción del mensaje falla, pueden recibir mensajes repetidamente .
[3] El problema del consumo de mensajes repetidos: debido a la existencia del paso 2, si un nodo consumidor se agota pero el consumo es exitoso, el middleware de mensajes entregará repetidamente el mensaje en este momento, lo que resultará en un consumo repetido del mensaje. Para resolver el problema del consumo repetido de mensajes, es necesario darse cuenta de la idempotencia del método de los participantes de la transacción .

En segundo lugar, la solución [solución de tabla de mensajes local  ]


EBay propuso originalmente la solución de tabla de mensajes local. El núcleo de esta solución es garantizar la coherencia de las operaciones y los mensajes del servicio de datos a través de transacciones locales , y luego enviar el mensaje al middleware de mensajes a través de una tarea programada y esperar hasta que el mensaje se envíe correctamente al consumidor. Borra el mensaje. El siguiente es un ejemplo de registro para enviar puntos para ilustrar: el siguiente ejemplo tiene dos interacciones de microservicio, servicio de usuario y servicio de punto, el servicio de usuario es responsable de agregar usuarios y el servicio de punto es responsable de agregar puntos.

[ El proceso de interacción es el siguiente ] : [1] Registro de usuario: el servicio de usuario agrega nuevos usuarios en asuntos locales y agrega "registro de mensajes puntuales". (Se garantiza que la tabla de usuarios y la tabla de mensajes sean coherentes a través de transacciones locales). A continuación se muestra el pseudocódigo. En este caso, la operación de la base de datos local y el registro de mensajes integral almacenado están en la misma transacción, y la operación de la base de datos local y la operación de registro de mensajes de grabación son atómicas.

begin transaction;

//1.新增用户

//2.存储积分消息日志

commit transation;

[ 2 ] Registro de análisis de tareas programadas: ¿Cómo garantizar que los mensajes se envíen a la cola de mensajes? Después del primer paso, el mensaje se ha escrito en la tabla de registro de mensajes y se puede iniciar un hilo independiente para escanear los mensajes en la tabla de registro de mensajes con regularidad y enviarlo al middleware de mensajes. El registro de mensajes se elimina después de que el middleware de mensajes informa que el envío se ha realizado correctamente; de ​​lo contrario Espere a que la tarea programada vuelva a intentarlo en el siguiente ciclo.
[3] Noticias para el consumidor: ¿Cómo garantizar que los consumidores puedan consumir noticias? Aquí puede utilizar el mecanismo ack ( confirmación del mensaje ) de MQ. Los consumidores supervisan MQ. Si el consumidor recibe el mensaje y se completa el procesamiento comercial, envía un ack (confirmación del mensaje) a MQ, lo que significa que el consumidor ha completado el consumo normal del mensaje, y MQ Ya no enviará mensajes a los consumidores; de lo contrario, los consumidores seguirán intentando enviar mensajes a los consumidores. El servicio de puntos recibe el mensaje "agregar puntos" y comienza a aumentar los puntos. Una vez que los puntos aumentan con éxito, el middleware del mensaje responderá con un ack, de lo contrario, el middleware del mensaje entregará el mensaje repetidamente. Dado que los mensajes se entregarán repetidamente, la función de "agregar puntos" del servicio de puntos debe ser idempotente .

En tercer lugar, la solución [programa de mensajes de transacciones de RocketMQ  ]


RocketMQ es un middleware de mensajería distribuida de Alibaba. Fue de código abierto en 2012 y se convirtió oficialmente en el principal proyecto de Apache en 2017. Se entiende que toda la línea de productos de mensajería de Alibaba Group, incluidos los productos de mensajería de Alibaba Cloud y las subsidiarias adquiridas, se ejecuta en RocketMQ, y RocketMQ ha logrado un rendimiento llamativo durante la promoción Double Eleven en los últimos años. Apache RocketMQ versión 4.3 o posterior admite oficialmente mensajes de transacciones , lo que brinda un soporte conveniente para la implementación de transacciones distribuidas. Mensajes de transacción RocketMQ diseñados principalmente para resolver átomos mensaje problema con el extremo ejecución Productor transaccional locales , RocketMQ diseñar  broker capacidad de comunicación bidireccional con el lado productor , de modo que el  corredor nació como un coordinador de transacciones existe ; mecanismo almacenado RocketMQ sí mismo proporciona Proporciona capacidades de persistencia para los mensajes de transacciones ; el mecanismo de alta disponibilidad de RocketMQ y el diseño confiable de mensajes garantizan que los mensajes transaccionales aún puedan garantizar la consistencia final de la transacción cuando el sistema es anormal. Después de RocketMQ 4.3, se implementa un mensaje de transacción completo, que en realidad es una encapsulación de la tabla de mensajes locales . La tabla de mensajes locales se mueve dentro del MQ para resolver el problema atómico del envío de mensajes y la ejecución de transacciones locales en el lado del productor.

[El proceso de ejecución es el siguiente ] : Para facilitar la comprensión, también utilizamos el ejemplo de registro para enviar puntos para describir todo el proceso. Producer es el remitente de MQ, en este caso es el servicio al usuario, responsable de agregar nuevos usuarios. El suscriptor de MQ es el consumidor de mensajes, en este ejemplo, es el servicio de puntos y es responsable de agregar puntos.
[1] El productor envía un mensaje de transacción: el productor (remitente de MQ) envía un mensaje de transacción al servidor de MQ, el servidor de MQ marca el estado del mensaje como Preparado (estado preparado), tenga en cuenta que este consumidor de mensajes (suscriptor de MQ) no puede consumir en este momento Llegado. En este ejemplo, el productor envía el "mensaje de agregar puntos" al servidor MQ.
[2] El servidor MQ responde al mensaje enviado correctamente: el servidor MQ recibe el mensaje enviado por el productor y responde con una respuesta satisfactoria. Indica que  MQ ha recibido el mensaje .
[3] El productor ejecuta transacciones locales: el productor ejecuta la lógica del código comercial y está controlado por las transacciones de la base de datos local. En este ejemplo, el productor realiza la operación de agregar usuario.
[4] Entrega del mensaje: si la transacción local del productor se ejecuta correctamente, enviará automáticamente un mensaje de confirmación a MQServer. Después de recibir el  mensaje de confirmación , MQ Server marcará el estado "Mensaje de aumento de puntos" como consumibleEn este momento, el suscriptor de MQ (servicio puntual) consume mensajes normalmente. Si la ejecución de la transacción local del Productor  falla, enviará automáticamente un mensaje de reversión a MQ Server y MQ Server eliminará el "Mensaje de aumento de puntos" después de recibir el mensaje de reversión . Los suscriptores de MQ (servicio puntual) consumen mensajes, y si el consumo es exitoso , responderán a MQ con ack , de lo contrario recibirán mensajes repetidamente. Aquí ack responde automáticamente de forma predeterminada, es decir, responde automáticamente a ack cuando el programa se ejecuta normalmente.
[5] Revisión de la transacción: Si el lado de ejecución se cuelga o se agota durante la ejecución de la transacción local en el lado del Productor, MQ Server consultará continuamente a otros Productores en el mismo grupo para obtener el estado de ejecución de la transacción. Este proceso se llama revisión de transacción . MQ Server decidirá si entrega el mensaje en función del resultado de la revisión de la transacción. El proceso principal anterior ha sido implementado por RocketMQ. En el lado del usuario, el usuario debe implementar la ejecución de la transacción local y los métodos de verificación de la transacción local , por lo que solo el estado de ejecución de la transacción local (mantener la tabla de estado de la transacción local) es suficiente . RoacketMQ proporciona la  interfaz RocketMQLocalTransactionListener:

public interface RocketMQLocalTransactionListener {

/**发送prepare消息成功此方法被回调,该方法用于执行本地事务

* @param msg 回传的消息,利用transactionId即可获取到该消息的唯一Id

* @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到

* @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调

*/

RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg);


/**@param msg 通过获取transactionId来判断这条消息的本地事务执行状态

* @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调

*/

RocketMQLocalTransactionState checkLocalTransaction(Message msg);

}

[6] Enviar mensajes de transacciones: la siguiente es la API proporcionada por RocketMQ para enviar mensajes de transacciones:

TransactionMQProducer producer = new TransactionMQProducer("ProducerGroup");

producer.setNamesrvAddr("127.0.0.1:9876");

producer.start();

//设置TransactionListener实现

producer.setTransactionListener(transactionListener);

//发送事务消息

SendResult sendResult = producer.sendMessageInTransaction(msg, null);

En cuarto lugar, RocketMQ realiza la última transacción consistente de mensajes confiables


[ Descripción del negocio ] A través del middleware RocketMQ, realiza la transacción distribuida final consistente de mensajes confiables y simula el proceso de transacción de transferencia de dos cuentas. Las dos cuentas están en diferentes bancos (Zhang San en bank1 y Li Si en bank2), bank1 y bank2 son dos microservicios. El proceso de transacción es que Zhang San transfiere una cantidad específica a Li Si. Los pasos de la transacción anterior, Zhang San deduciendo la cantidad y enviando un mensaje de transferencia al banco2, las dos operaciones deben ser una transacción integral.

[ Componentes del programa ]:

Base de datos: MySQL-5.7.25

Incluyendo dos bases de datos bank1 y bank2.

JDK: 64 位 jdk1.8.0_201

servidor rocketmq: RocketMQ-4.5.0

cliente rocketmq: RocketMQ-Spring-Boot-starter.2.0.2-RELEASE

Marco de microservicio: spring-boot-2.1.3, spring-cloud-Greenwich.RELEASE

La relación entre microservicios y bases de datos:

dtx / dtx-txmsg-demo / dtx-txmsg-demo-bank1 Banco 1, operar la cuenta Zhang San, conectarse a la base de datos bank1

dtx / dtx-txmsg-demo / dtx-txmsg-demo-bank2 Banco 2, operar cuenta Li Si, conectarse a la base de datos bank2

[ Código básico ] : La estructura técnica del programa es la siguiente:

[El proceso de interacción es el siguiente ] :

[1] Bank1 envía un mensaje de transferencia al servidor MQ;
[2] Bank1 realiza transacciones locales y
deduce el monto; [3] Bank2 recibe mensajes, realiza transacciones locales y agrega el monto;
[ Base de datos ] : agregar des_duplication en las bases de datos bank1 y bank2 , Tabla de registro de transacciones (tabla de deduplicación), utilizada para el control idempotente de transacciones .

DROP TABLE IF EXISTS `de_duplication`;

CREATE TABLE `de_duplication` (

`tx_no` varchar(64) COLLATE utf8_bin NOT NULL,

`create_time` datetime(0) NULL DEFAULT NULL,

PRIMARY KEY (`tx_no`) USING BTREE

) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

[ Dependencia de la versión ] : la versión de rocketmq-spring-boot-starter se especifica en el proyecto principal

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

[ Configuración rocketMQ ] : configure la dirección del servidor de nombres rocketMQ y el grupo de producción en application-local.propertis.

rocketmq.producer.group = producer_bank2

rocketmq.name-server = 127.0.0.1:9876

[ Código de capa de servicio de Zhang San ] :

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


/**
 * @author Administrator
 * @version 1.0
**/
@Service
@Slf4j
public class AccountInfoServiceImpl implements AccountInfoService {

    @Autowired
    AccountInfoDao accountInfoDao;

    @Autowired
    RocketMQTemplate rocketMQTemplate;

    //向mq发送转账消息
    @Override
    public void sendUpdateAccountBalance(AccountChangeEvent accountChangeEvent) {
        //将accountChangeEvent转成json
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("accountChange", accountChangeEvent);
        String jsonString = jsonObject.toJSONString();
        //生成message类型
        Message<String> message = MessageBuilder.withPayload(jsonString).build();
        //发送一条事务消息

        /**
         * String txProducerGroup 生产组
         * String destination topic,
         * Message<?> message, 消息内容
         * Object arg 参数
         */
        rocketMQTemplate.sendMessageInTransaction("producer_group_txmsg_bank1", "topic_txmsg", message, null);

    }


    //更新账户,扣减金额
    @Override
    @Transactional
    public void doUpdateAccountBalance(AccountChangeEvent accountChangeEvent) {
        //幂等判断,txNo是在Ctroller中生成的 UUID,全局唯一
        if (accountInfoDao.isExistTx(accountChangeEvent.getTxNo()) > 0) {
            return;
        }

        //扣减金额
        accountInfoDao.updateAccountBalance(accountChangeEvent.getAccountNo(), accountChangeEvent.getAmount() * -1);
        //添加事务日志
        accountInfoDao.addTx(accountChangeEvent.getTxNo());
        if (accountChangeEvent.getAmount() == 3) {
            throw new RuntimeException("人为制造异常");
        }

    }

}

[ Zhang San RocketMQLocalTransactionListener ] : escriba la clase de implementación de la interfaz RocketMQLocalTransactionListener para implementar los dos métodos de ejecución de transacciones locales y revisión de transacciones.

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;


/**
 * @author Administrator
 * @version 1.0
 **/
@Component
@Slf4j
//生产者组与发送消息时定义组相同
@RocketMQTransactionListener(txProducerGroup = "producer_group_txmsg_bank1")
public class ProducerTxmsgListener implements RocketMQLocalTransactionListener {

    @Autowired
    AccountInfoService accountInfoService;

    @Autowired
    AccountInfoDao accountInfoDao;

    //事务消息发送后的回调方法,当消息发送给mq成功,此方法被回调
    @Override
    @Transactional
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        try {
            //解析message,转成AccountChangeEvent
            String messageString = new String((byte[]) message.getPayload());
            JSONObject jsonObject = JSONObject.parseObject(messageString);
            String accountChangeString = jsonObject.getString("accountChange");
            //将accountChange(json)转成AccountChangeEvent
            AccountChangeEvent accountChangeEvent = JSONObject.parseObject(accountChangeString, AccountChangeEvent.class);
            //执行本地事务,扣减金额
            accountInfoService.doUpdateAccountBalance(accountChangeEvent);
            //当返回RocketMQLocalTransactionState.COMMIT,自动向mq发送commit消息,mq将消息的状态改为可消费
            return RocketMQLocalTransactionState.COMMIT;

        } catch (Exception e) {
            e.printStackTrace();
            return RocketMQLocalTransactionState.ROLLBACK;
        }


    }


    //事务状态回查,查询是否扣减金额
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        //解析message,转成AccountChangeEvent
        String messageString = new String((byte[]) message.getPayload());
        JSONObject jsonObject = JSONObject.parseObject(messageString);
        String accountChangeString = jsonObject.getString("accountChange");
        //将accountChange(json)转成AccountChangeEvent
        AccountChangeEvent accountChangeEvent = JSONObject.parseObject(accountChangeString, AccountChangeEvent.class);
        //事务id
        String txNo = accountChangeEvent.getTxNo();
        int existTx = accountInfoDao.isExistTx(txNo);
        if (existTx > 0) {
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }

}

[ Código de capa de servicio Li Si ] :

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author Administrator
 * @version 1.0
 **/
@Service
@Slf4j
public class AccountInfoServiceImpl implements AccountInfoService {

    @Autowired
    AccountInfoDao accountInfoDao;

    //更新账户,增加金额
    @Override
    @Transactional
    public void addAccountInfoBalance(AccountChangeEvent accountChangeEvent) {
        log.info("bank2更新本地账号,账号:{},金额:{}", accountChangeEvent.getAccountNo(), accountChangeEvent.getAmount());
        if (accountInfoDao.isExistTx(accountChangeEvent.getTxNo()) > 0) {
            return;
        }
        //增加金额
        accountInfoDao.updateAccountBalance(accountChangeEvent.getAccountNo(), accountChangeEvent.getAmount());
        //添加事务记录,用于幂等
        accountInfoDao.addTx(accountChangeEvent.getTxNo());
        if (accountChangeEvent.getAmount() == 4) {
            throw new RuntimeException("人为制造异常");
        }

    }

}

[Clase de monitor MQ]:  monitorice el tema de destino implementando la interfaz RocketMQListener

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author Administrator
 * @version 1.0
 **/
@Component
@Slf4j
@RocketMQMessageListener(consumerGroup = "consumer_group_txmsg_bank2", topic = "topic_txmsg")
public class TxmsgConsumer implements RocketMQListener<String> {
    
    @Autowired
    AccountInfoService accountInfoService;

    //接收消息
    @Override
    public void onMessage(String message) {
        log.info("开始消费消息:{}", message);
        //解析消息
        JSONObject jsonObject = JSONObject.parseObject(message);
        String accountChangeString = jsonObject.getString("accountChange");
        //转成AccountChangeEvent
        AccountChangeEvent accountChangeEvent = JSONObject.parseObject(accountChangeString, AccountChangeEvent.class);
        //设置账号为李四的
        accountChangeEvent.setAccountNo("2");
        //更新本地账户,增加金额
        accountInfoService.addAccountInfoBalance(accountChangeEvent);
        
    }

}

Cinco, resumen


La consistencia máxima de los mensajes confiables es asegurar la consistencia de los mensajes del productor al consumidor a través del middleware de mensajes. En este caso, RocketMQ se utiliza como middleware de mensajes. RocketMQ resuelve principalmente dos funciones:
[1] Transacciones locales y envío de mensajes Problemas atómicos ;
[2] La confiabilidad del mensaje recibido por los participantes de la transacción , la

consistencia eventual de mensajes confiables es adecuada para escenarios con ciclos de ejecución largos y bajos requisitos de tiempo real . Después de la introducción del mecanismo de mensajes, la operación de transacción síncrona se convierte en la operación asíncrona basada en la ejecución del mensaje, lo que evita la influencia de la operación de bloqueo síncrono en la transacción distribuida y realiza el desacoplamiento de los dos servicios.

original

● La optimización de rendimiento de Tomcat8 más sólida de la historia

¿Por qué Alibaba puede resistir 10 mil millones en 90 segundos? - La evolución de la arquitectura distribuida de alta concurrencia del lado del servidor

Plataforma de comercio electrónico B2B: función de pago electrónico ChinaPay UnionPay

Aprenda el candado distribuido de Zookeeper, deje que los entrevistadores lo miren con admiración

Solución de bloqueo distribuido de Redisson con microservicio de pico de comercio electrónico de SpringCloud

Vea más artículos buenos, ingrese a la cuenta oficial, por favor, excelente en el pasado

Una cuenta pública profunda y conmovedora 0.0

Supongo que te gusta

Origin blog.csdn.net/a1036645146/article/details/109362669
Recomendado
Clasificación