发送半消息
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开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
总结
其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。
这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来
目录:
部分内容截图:
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。
这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来
目录:
[外链图片转存中…(img-UOivKqw0-1713283002658)]
部分内容截图:
[外链图片转存中…(img-EhJsrXC3-1713283002658)]
[外链图片转存中…(img-ZSdShkSr-1713283002658)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!