官方文档:特性设计 第五段主要介绍了事务消息的设计与处理
整体设计思想是2pc实现,外加补偿逻辑处理二阶段的超时或者失败的消息。
原理图如下:
详细设计思路请参考上面链接,主要实现简单说明:
生产者
TransactionMQProducer:sendMessageInTransaction方法生产发送事务消息,此时的消息是不可见的对于消费者。broker收到消息后需要到事务监听实现类中进行事务状态的确认,是回滚还是提交。
事务监听
实现TransactionListener接口,其中两个方法:
-
public LocalTransactionState executeLocalTransaction(Message message, Object o)
执行本地事务,发送half消息到rocketmq的固定队列,返回值决定消息是否回滚 -
public LocalTransactionState checkLocalTransaction(MessageExt messageExt)
提供回调,当broker服务器收不到消息是回滚还是提交的回应时,会回调这个方法询问,可以在这里处理消息事务状态,当依然无法得知确认消息状态时还是会回滚消息。
下述案例使用rocketmq client,未直接使用Springboot提供。
与Spring集成 注入TransactionMQProducer :
@Configuration
@Slf4j
public class ProduceConfig {
@Value("${rocketmq.name-server}")
private String nameServer;
@Bean
public AccountRocketMQTransactionLister accountRocketMQTransactionLister(ApplicationContext context) {
return new AccountRocketMQTransactionLister(context.getBean(IAccountService.class));
}
@Bean(name = "accountRocketMQTemplate")
@Primary
public TransactionMQProducer accountRocketMQTemplate(ApplicationContext context) throws MQClientException {
TransactionMQProducer producer = new TransactionMQProducer();
producer.setTransactionListener(accountRocketMQTransactionLister(context));
producer.setNamesrvAddr(nameServer);
producer.setProducerGroup("account-gp");
producer.start();
System.out.println("transaction producer start...");
return producer;
}
@Bean(name = "traceProducer")
public DefaultMQProducer traceProducer() throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer("trace-producer-group", true, "trace-topic");
producer.setNamesrvAddr(nameServer);
producer.start();
return producer;
}
}
生产者:
@PostMapping("/another-trancase1")
public ResponseResult tranA1(@RequestBody AccountVo accountVo) {
try {
Message message = new Message();
message.setBody(accountVo.getUserId().getBytes());
message.setTopic("account1");
message.setTags("debit");
TransactionSendResult sendResult = null;
try {
sendResult = accountRocketMQTemplate.sendMessageInTransaction(message, accountVo);
} catch (MQClientException e) {
log.error("{}", e);
throw new BaseException("生产消息失败");
}
return ResultUtil.success("下单成功", sendResult);
} catch (Exception e) {
e.printStackTrace();
}
return ResultUtil.error("下单失败", null);
}
事务监听
@Slf4j
public class AccountRocketMQTransactionLister implements TransactionListener {
private IAccountService accountService;
public AccountRocketMQTransactionLister(IAccountService accountService){
this.accountService = accountService;
}
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
String transactionId = message.getTransactionId();
log.info("AccountTransactionLister事务Id:{}", transactionId);
return check(message, o);
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
String transactionId = messageExt.getTransactionId();
log.info("AccountTransactionLister事务Id:{}", transactionId);
return check(messageExt);
}
private LocalTransactionState check(Message message, Object o) {
String userId = new String(message.getBody());
AccountTbl accountTbl = new AccountTbl();
accountTbl.setUserId(userId);
AccountTbl result = accountService.get(accountTbl);
if (result == null) {
log.error("无账户信息");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
if (result.getMoney() < 0) {
log.error("账户余额不足");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.COMMIT_MESSAGE;
}
private LocalTransactionState check(MessageExt message) {
String userId = new String(message.getBody());
AccountTbl accountTbl = new AccountTbl();
accountTbl.setUserId(userId);
AccountTbl result = accountService.get(accountTbl);
if (result == null) {
log.error("无账户信息");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
if (result.getMoney() < 0) {
log.error("账户余额不足");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.COMMIT_MESSAGE;
}
}
消费者:
@Service
@Slf4j
public class RocketAccountConsumerService implements MessageListenerConcurrently {
@Autowired
private IAccountService accountService;
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
if (!CollectionUtils.isEmpty(list)) {
try {
for (MessageExt messageExt : list) {
String userId = new String(messageExt.getBody());
log.info("获取到扣费通知:{}", userId);
AccountVo accountVo = new AccountVo();
accountVo.setUserId(userId);
accountVo.setMoney(10);
accountService.debit(accountVo.getUserId(), accountVo.getMoney());
}
} catch (Exception e) {
log.error("处理异常:{}", e);
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}