RocketMQ进阶-事务消息

发送半消息

order-service在执行删除订单操作时发送一条半消息给MQServer,发送半消息主要是使用rocketMQTemplate.sendMessageInTransaction() 方法,发送事务消息。

@Override

public void delete(String orderNo) {

Order order = orderMapper.selectByNo(orderNo);

//如果订单存在且状态为有效,进行业务处理

if (order != null && CloudConstant.VALID_STATUS.equals(order.getStatus())) {

String transactionId = UUID.randomUUID().toString();

//如果可以删除订单则发送消息给rocketmq,让用户中心消费消息

rocketMQTemplate.sendMessageInTransaction(“add-amount”,

MessageBuilder.withPayload(

UserAddMoneyDTO.builder()

.userCode(order.getAccountCode())

.amount(order.getAmount())

.build()

)

.setHeader(RocketMQHeaders.TRANSACTION_ID, transactionId)

.setHeader(“order_id”,order.getId())

.build()

,order

);

}

}

首先先校验一下订单状态,然后发送消息给MQServer,这个逻辑大家都看得懂,主要是关注sendMessageInTransaction() 方法,源码如下:

public TransactionSendResult sendMessageInTransaction(String destination, Message<?> message, Object arg) throws MessagingException {

try {

if (((TransactionMQProducer)this.producer).getTransactionListener() == null) {

throw new IllegalStateException(“The rocketMQTemplate does not exist TransactionListener”);

} else {

org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message);

return this.producer.sendMessageInTransaction(rocketMsg, arg);

}

} catch (MQClientException var5) {

throw RocketMQUtil.convert(var5);

}

}

该方法有三个参数:

  • destination:目的地(主题),这里发送给add-amount 这个主题

  • message:发送给消费者的消息体,需要使用 MessageBuilder.withPayload() 来构建消息

  • arg:参数

注意,这里我们生成了一个transactionId,并放在header中跟消息一起发送(这里实际也可以构造成一个对象,放在arg里进行发送),作用后面再讲!

执行本地事务与回查

MQServer收到半消息后会告诉生产者order-service确认收到半消息,这时候order-service需要执行本地事务,执行完本地事务后再告诉MQServer本地事务的执行状态,确认消息究竟是Commit还是Rollback。如果在告诉MQServer本地执行状态的时候出异常了还需要让MQServer能够回查到,怎么实现这一些列操作呢?

RocketMQ提供了 RocketMQLocalTransactionListener 接口,本地事务监听器,这个接口类的实现如下:

第一个方法executeLocalTransaction 为执行本地事务;

第二个方法checkLocalTransaction 为检查本地事务的执行状态,也就是回查动作。

有了这个接口类我们的执行逻辑清楚了,但是还有个问题:本地事务已经执行完成了,怎么去回查本地事务的执行结果呢?

我们可以在执行本地事务的时候同时生成一个事务日志,让本地事务与日志事务在同一个方法中,同时添加@Transactional 注解,保证两个操作事务是一个原子操作。这样如果事务日志表中有这个本地事务的信息,那就代表本地事务执行成功,需要Commit,相反如果没有对应的事务日志,则表示没执行成功,需要Rollback

思路既然理顺了,咱们就开撸。

  • 首先创建一个日志表

很简单的三个字段,主要是这个事务id,需要根据这个事务id回查事务,还记得我们在发送半消息时生成的事务id吗,就是干这个用的!

  • 在生产者编写方法实现RocketMQLocalTransactionListener

@Slf4j

@RocketMQTransactionListener

@RequiredArgsConstructor(onConstructor = @__(@Autowired))

public class AddUserAmountListener implements RocketMQLocalTransactionListener {

private final OrderService orderService;

private final RocketMqTransactionLogMapper rocketMqTransactionLogMapper;

/**

  • 执行本地事务

*/

@Override

public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object arg) {

log.info(“执行本地事务”);

MessageHeaders headers = message.getHeaders();

//获取事务ID

String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);

Integer orderId = Integer.valueOf((String)headers.get(“order_id”));

log.info(“transactionId is {}, orderId is {}”,transactionId,orderId);

try{

//执行本地事务,并记录日志

orderService.changeStatuswithRocketMqLog(orderId, CloudConstant.INVALID_STATUS,transactionId);

//执行成功,可以提交事务

return RocketMQLocalTransactionState.COMMIT;

}catch (Exception e){

return RocketMQLocalTransactionState.ROLLBACK;

}

}

/**

  • 本地事务的检查,检查本地事务是否成功

*/

@Override

public RocketMQLocalTransactionState checkLocalTransaction(Message message) {

MessageHeaders headers = message.getHeaders();

//获取事务ID

String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);

log.info(“检查本地事务,事务ID:{}”,transactionId);

//根据事务id从日志表检索

QueryWrapper queryWrapper = new QueryWrapper<>();

queryWrapper.eq(“transaction_id”,transactionId);

RocketmqTransactionLog rocketmqTransactionLog = rocketMqTransactionLogMapper.selectOne(queryWrapper);

if(null != rocketmqTransactionLog){

return RocketMQLocalTransactionState.COMMIT;

}

return RocketMQLocalTransactionState.ROLLBACK;

}

}

  • 执行本地事务的方法

@Transactional(rollbackFor = RuntimeException.class)

@Override

public void changeStatuswithRocketMqLog(Integer id,String status,String transactionId){

//将订单状态置位无效

orderMapper.changeStatus(id,status);

//插入事务表

rocketMqTransactionLogMapper.insert(

RocketmqTransactionLog.builder()

.transactionId(transactionId)

.log(“执行删除订单操作”)

.build()

);

}

这一块的代码逻辑都是在生产端,即Order-Server,大家不要搞错了

消费消息

Rollback的消息MQServer会给我们处理,我们只要关注Commit状态时消费端可以正常消费即可。在account-service监听消息,如果收到消息则给用户账户增加余额。

@Slf4j

@Service

@RocketMQMessageListener(topic = “add-amount”,consumerGroup = “cloud-group”)

@RequiredArgsConstructor(onConstructor = @__(@Autowired) )

public class AddUserAmountListener implements RocketMQListener {

private final AccountMapper accountMapper;

/**

  • 收到消息的业务逻辑

*/

@Override

public void onMessage(UserAddMoneyDTO userAddMoneyDTO) {

log.info(“received message: {}”,userAddMoneyDTO);

accountMapper.increaseAmount(userAddMoneyDTO.getUserCode(),userAddMoneyDTO.getAmount());

log.info(“add money success”);

}

}

测试

订单表有这样一条记录,用户为jianzh5,amount为200

用户表的记录,执行完成后jianzh5的账户应该变成250

  • 调用删除订单接口,删除订单

  • 发送半消息

  • 执行本地事务,并生成事务日志

  • 模拟异常情况 在发送Commit消息的时候我们用命令杀掉进程taskkill /pid 19748 -t -f,模拟异常!

  • 重新启动order-service,查看是否会执行回查动作MQServer进行回查,检查事务日志,判断是否可以提交事务

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

[外链图片转存中…(img-PQgLLdNg-1715570484545)]

[外链图片转存中…(img-F7PsFwzh-1715570484545)]

[外链图片转存中…(img-f6UwxyPI-1715570484546)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 28
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值