índice
1. Uma visão geral da eventual consistência de mensagens confiáveis
Em segundo lugar, a solução [solução de tabela de mensagem local]
Terceiro, a solução [programa de mensagem de transação RocketMQ]
Quatro, RocketMQ realiza transação consistente final de mensagem confiável
1. Uma visão geral da eventual consistência de mensagens confiáveis
Esquema de consistência eventual de mensagem confiável significa que quando o iniciador da transação completa a transação local e envia uma mensagem, o participante da transação (consumidor da mensagem) deve ser capaz de receber a mensagem e processar a transação com sucesso. Este esquema enfatiza que, desde que a mensagem seja enviada ao participante da transação Os assuntos finais das partes devem ser consistentes . Este programa é concluído usando o middleware de mensagem, conforme mostrado na figura a seguir:
O iniciador da transação (produtor da mensagem) envia a mensagem para o middleware da mensagem, o participante da transação recebe a mensagem do middleware da mensagem, entre o iniciador da transação e o middleware da mensagem e entre o participante da transação (consumidor da mensagem) e o middleware da mensagem Toda a comunicação é feita pela rede e a incerteza da comunicação da rede causará problemas de transação distribuída. Portanto, a eventual consistência de mensagens confiáveis deve resolver os seguintes problemas:
[1] A atomicidade das transações locais e do envio da mensagem : o iniciador da transação deve enviar a mensagem após a transação local ser executada com sucesso, caso contrário, a mensagem é descartada. Ou seja, para atingir a atomicidade das transações locais e do envio de mensagens , todas são bem-sucedidas ou ambas falham. A atomicidade das transações locais e do envio de mensagens é a questão chave para perceber a consistência eventual de mensagens confiáveis . Vamos tentar este tipo de operação primeiro, enviar primeiro a mensagem e depois operar o banco de dados : neste caso, a consistência da operação do banco de dados e da mensagem enviada não pode ser garantida, porque a mensagem pode ser enviada com sucesso e a operação do banco de dados falhou.
begin transaction;
//1.发送MQ
//2.数据库操作
commit transation;
A segunda solução é executar as operações do banco de dados primeiro e, em seguida, enviar as mensagens : neste caso, parece não haver problema.Se a mensagem MQ falhar ao ser enviada, uma exceção será lançada, fazendo com que a transação do banco de dados retroceda. mas:
- Se for uma exceção de tempo limite , o banco de dados será retrocedido, mas o MQ foi enviado normalmente, o que também causará inconsistências.
- Se a mensagem for enviada com sucesso, a JVM trava repentinamente quando a transação é finalmente confirmada e a transação não é confirmada com sucesso, resultando em inconsistência de dados entre os dois sistemas.
begin transaction;
//1.数据库操作
//2.发送MQ
commit transation;
[2] Confiabilidade da mensagem recebida pelos participantes da transação : Os participantes da transação devem ser capazes de receber mensagens da fila de mensagens, caso a recepção falhe, eles podem receber mensagens repetidamente .
[3] O problema do consumo repetido de mensagens: devido à existência da etapa 2, se um nó consumidor atingir o tempo limite, mas o consumo for bem-sucedido, o middleware da mensagem entregará repetidamente a mensagem neste momento, resultando no consumo repetido da mensagem. Para resolver o problema de consumo repetido de mensagens, é necessário perceber a idempotência do método dos participantes da transação .
Em segundo lugar, a solução [solução de tabela de mensagem local ]
A solução de tabela de mensagem local foi proposta originalmente pelo eBay. O núcleo desta solução é garantir a consistência das operações de serviço de dados e mensagens por meio de transações locais e, em seguida, enviar a mensagem para o middleware de mensagem por meio de uma tarefa cronometrada e esperar até que a mensagem seja enviada com sucesso ao consumidor. Exclua a mensagem. A seguir está um exemplo de registro para enviar pontos para ilustrar: o exemplo a seguir tem duas interações de microsserviço, serviço de usuário e serviço de ponto, serviço de usuário é responsável por adicionar usuários e serviço de ponto é responsável por adicionar pontos.
[ O processo de interação é o seguinte ] : [1] Registro do usuário: O serviço do usuário adiciona novos usuários nos assuntos locais e adiciona "registro de mensagem de ponto". (A tabela de usuário e a tabela de mensagem têm a garantia de ser consistente por meio de transações locais.) Abaixo está o pseudocódigo. Nesse caso, a operação do banco de dados local e o log de mensagem integral armazenado estão na mesma transação, e a operação do banco de dados local e a operação do log de mensagem de gravação são atômicos.
begin transaction;
//1.新增用户
//2.存储积分消息日志
commit transation;
[ 2 ] Registro de verificação de tarefa agendada: Como garantir que as mensagens sejam enviadas para a fila de mensagens? Após a primeira etapa, a mensagem foi gravada na tabela de log de mensagens e um thread independente pode ser iniciado para varrer as mensagens na tabela de log de mensagens regularmente e enviá-las para o middleware de mensagens. O log de mensagens é excluído após o middleware de mensagens relatar que o envio foi bem-sucedido, caso contrário Aguarde até que a tarefa agendada tente novamente no próximo ciclo.
[3] Notícias do consumidor: como garantir que os consumidores possam consumir notícias? Aqui você pode usar o mecanismo ack ( confirmação de mensagem ) do MQ. Os consumidores monitoram o MQ. Se o consumidor receber a mensagem e o processamento de negócios for concluído, ele enviará um ack (confirmação da mensagem) para o MQ, o que significa que o consumidor concluiu o consumo normal da mensagem e MQ Não enviará mais mensagens aos consumidores, caso contrário, os consumidores continuarão a tentar enviar mensagens aos consumidores. O serviço de ponto recebe a mensagem "adicionar pontos" e começa a aumentar os pontos. Após os pontos aumentarem com sucesso, o middleware da mensagem responderá com um ack, caso contrário, o middleware da mensagem entregará a mensagem repetidamente. Visto que as mensagens serão entregues repetidamente, a função "adicionar pontos" do serviço de ponto precisa ser idempotente .
Terceiro, a solução [programa de mensagem de transação RocketMQ ]
RocketMQ é um middleware de mensagens distribuído do Alibaba. Ele foi aberto em 2012 e tornou-se oficialmente o principal projeto Apache em 2017. Entende-se que toda a linha de produtos de mensagens do Alibaba Group, incluindo produtos de mensagens da Alibaba Cloud e subsidiárias adquiridas, funciona no RocketMQ, e o RocketMQ alcançou um desempenho atraente durante a promoção Double Eleven nos últimos anos. O Apache RocketMQ versão 4.3 ou posterior oferece suporte oficial para mensagens de transação , fornecendo suporte conveniente para implementação de transação distribuída. Mensagens de transação RocketMQ projetadas principalmente para resolver mensagem de problema de átomos com a execução transacional local Fim do produtor , RocketMQ projeta o corretor de capacidade de comunicação bidirecional com o lado do produtor , para que o corretor nascido como um coordenador de transação exista ; o mecanismo armazenado que o próprio RocketMQ fornece Fornece recursos de persistência para mensagens de transação ; o mecanismo de alta disponibilidade do RocketMQ e o design de mensagem confiável garantem que as mensagens transacionais ainda possam garantir a consistência final da transação quando o sistema estiver anormal. Após o RocketMQ 4.3, uma mensagem de transação completa é implementada, que na verdade é um encapsulamento da tabela de mensagem local.A tabela de mensagem local é movida dentro do MQ para resolver o problema atômico de envio de mensagem e execução de transação local no lado do produtor.
[O processo de execução é o seguinte ] : Para facilitar o entendimento, também usamos o exemplo de cadastro para enviar pontos para descrever todo o processo. O produtor é o emissor do MQ, neste caso é o serviço ao usuário, responsável por adicionar novos usuários. O assinante MQ é o consumidor da mensagem.Neste exemplo, é o serviço de ponto e é responsável por adicionar pontos.
[1] Produtor envia mensagem de transação: Produtor (remetente MQ) envia mensagem de transação para MQ Server, MQ Server marca o status da mensagem como Preparado (estado preparado), observe que este consumidor de mensagem (assinante MQ) não pode consumir neste momento Chegou. Neste exemplo, o produtor envia a "mensagem de adição de pontos" ao MQ Server.
[2] O MQ Server responde à mensagem enviada com sucesso: o MQ Server recebe a mensagem enviada pelo produtor e responde com uma resposta bem-sucedida. Indica que o MQ recebeu a mensagem .
[3] O produtor executa transações locais: o produtor executa a lógica do código de negócios e é controlado pelas transações do banco de dados local. Neste exemplo, o produtor executa a operação de adição de usuário.
[4] Entrega de mensagem: Se a transação local do produtor for executada com sucesso, ele enviará automaticamente uma mensagem de confirmação para MQServer. Após receber a mensagem de confirmação , o servidor MQ marcará o status "Mensagem de aumento de pontos" como consumível, Neste momento, o assinante MQ (serviço pontual) consome mensagens normalmente. Se a execução da transação local do Produtor falhar, ele enviará automaticamente uma mensagem de rollback para o MQ Server, e o MQ Server excluirá a "Mensagem de pontos de aumento" após receber a mensagem de rollback . Os assinantes do MQ (serviço de ponto) consomem mensagens e, se o consumo for bem-sucedido , eles responderão ao MQ com ack , caso contrário, receberão mensagens repetidamente. Aqui, o ack responde automaticamente por padrão, ou seja, responde automaticamente ao ack quando o programa é executado normalmente.
[5] Revisão da transação: Se o lado da execução travar ou atingir o tempo limite durante a execução da transação local no lado do produtor, o MQ Server consultará continuamente outros produtores no mesmo grupo para obter o status de execução da transação.Este processo é chamado de revisão da transação . O MQ Server decidirá se deve entregar a mensagem com base no resultado da revisão da transação. O processo principal acima foi implementado pela RocketMQ. No lado do usuário, o usuário precisa implementar a execução da transação local e métodos de verificação de volta da transação local , portanto, apenas o status de execução da transação local (manter a tabela de status da transação local) é suficiente . RoacketMQ fornece interface 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 mensagens de transação: A seguir está a API fornecida pela RocketMQ para enviar mensagens de transação:
TransactionMQProducer producer = new TransactionMQProducer("ProducerGroup");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
//设置TransactionListener实现
producer.setTransactionListener(transactionListener);
//发送事务消息
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
Quarto, RocketMQ realiza a transação consistente final de mensagens confiáveis
[ Descrição do negócio ] Por meio do middleware RocketMQ, ele realiza a transação distribuída consistente final de mensagens confiáveis e simula o processo de transação de transferência de duas contas. As duas contas estão em bancos diferentes (Zhang San no banco1 e Li Si no banco2), banco1 e banco2 são dois microsserviços. O processo de transação é que Zhang San transfere uma determinada quantia para Li Si. As etapas da transação acima, Zhang San deduzindo o valor e enviando uma mensagem de transferência para o banco2, as duas operações devem ser uma transação integral.
[ Componentes do programa ]:
Banco de dados: MySQL-5.7.25
Incluindo dois bancos de dados bank1 e bank2.
JDK : 64 位 jdk1.8.0_201
servidor rocketmq: RocketMQ-4.5.0
cliente rocketmq: RocketMQ-Spring-Boot-starter.2.0.2-RELEASE
Estrutura de microsserviço: spring-boot-2.1.3, spring-cloud-Greenwich.RELEASE
A relação entre microsserviços e bancos de dados:
dtx / dtx-txmsg-demo / dtx-txmsg-demo-bank1 Banco 1, operar conta Zhang San, conectar ao banco de dados banco1
dtx / dtx-txmsg-demo / dtx-txmsg-demo-bank2 Banco 2, operar conta Li Si, conectar-se ao banco de dados banco2
[ Código principal ] : A estrutura técnica do programa é a seguinte:
[O processo de interação é o seguinte ] :
[1] Banco1 envia uma mensagem de transferência para o servidor MQ;
[2] Banco1 realiza transações locais e
deduz o valor; [3] Banco2 recebe mensagens, realiza transações locais e adiciona o valor;
[ Banco de dados ] : Adicionar de_duplicação nos bancos de dados banco1 e banco2 , Tabela de registro de transações (tabela de deduplicação), usada para controle idempotente de transações .
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;
[ Dependência de versão ] : a versão de rocketmq-spring-boot-starter é especificada no projeto pai
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
[ Configuração rocketMQ ] : Configure o endereço do rocketMQ nameServer e o grupo de produção em application-local.propertis.
rocketmq.producer.group = producer_bank2
rocketmq.name-server = 127.0.0.1:9876
[ Código da camada de serviço 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 ] : Escreva a classe de implementação da interface RocketMQLocalTransactionListener para implementar dois métodos de execução de transações locais e revisão de transações.
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 da camada de serviço 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("人为制造异常");
}
}
}
[Classe do monitor MQ]: monitore o tópico de destino implementando a interface 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, resumo
A consistência final de mensagens confiáveis é garantir a consistência das mensagens do produtor ao consumidor por meio do middleware da mensagem. Nesse caso, RocketMQ é usado como o middleware da mensagem. RocketMQ resolve principalmente duas funções:
[1] Transações locais e envio de mensagens Problemas atômicos ;
[2] A confiabilidade da mensagem recebida pelos participantes da transação ; a
eventual consistência de mensagens confiáveis é adequada para cenários com longos ciclos de execução e baixos requisitos de tempo real . Após a introdução do mecanismo de mensagem, a operação de transação síncrona passa a ser a operação assíncrona baseada na execução da mensagem, o que evita a influência da operação de bloqueio síncrona na transação distribuída e realiza o desacoplamento dos dois serviços.
● A otimização de desempenho do Tomcat8 mais forte da história
● Por que o Alibaba pode resistir a 10 bilhões em 90 segundos? - A evolução da arquitetura distribuída de alta simultaneidade do lado do servidor
● Plataforma de comércio eletrônico B2B - função de pagamento eletrônico ChinaPay UnionPay
● Solução de bloqueio distribuído de microsserviço do SpringCloud e-commerce de pico de Redisson
Confira mais bons artigos, entre na conta oficial - por favor - excelente no passado
Uma conta pública profunda e comovente 0.0