1. 在父pom文件中引入依赖:
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
2.创建事务监听类 用于事务消息回调
package com.futurecloud.myTransactionListener;
/**
* @author huangjialin
* @date 2023/1/12
* @description:
*/
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.futurecloud.dao.operation.AppPackageInfoDao;
import com.futurecloud.dao.sys.SysLogDao;
import com.futurecloud.entity.po.sys.SysLogEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
/**
* rocketmq 事务消息回调类
*/
@Slf4j
@Component
public class OrderTransactionListener implements TransactionListener {
@Resource
private SysLogDao sysLogDao;
/**
* half消息发送成功后回调此方法,执行本地事务
*
* @param message 回传的消息,利用transactionId即可获取到该消息的唯一Id
* @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
* @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
*/
@Override
@Transactional
public LocalTransactionState executeLocalTransaction(Message message, Object arg) {
long startTime = System.currentTimeMillis();
log.info("开始执行本地事务:订单信息:" + new String(message.getBody()));
String msgKey = new String(message.getBody());
SysLogEntity sysLogEntity = JSONObject.parseObject(msgKey, SysLogEntity.class);
int saveResult;
LocalTransactionState state;
try {
//修改为true时,模拟本地事务异常
// boolean imitateException = true;
// if(imitateException) {
// throw new RuntimeException("更新本地事务时抛出异常");
// }
// 生成订单,本地事务的回滚依赖于DB的ACID特性,所以需要添加Transactional注解。当本地事务提交失败时,返回ROLLBACK_MESSAGE,则会回滚rocketMQ中的half message,保证分布式事务的一致性。
sysLogEntity.setTime(System.currentTimeMillis()-startTime);
saveResult = sysLogDao.insert(sysLogEntity);
state = saveResult == 1 ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;
// 更新本地事务并将事务号持久化,为后续的幂等做准备
// TransactionDao.add(transactionId)
} catch (Exception e){
log.error("本地事务执行异常,异常信息:", e);
state = LocalTransactionState.ROLLBACK_MESSAGE;
}
//修改为true时,模拟本地事务超时,对于超时的消息,rocketmq会调用 checkLocalTransaction 方法回查本地事务执行状况
// boolean imitateTimeout = true;
// if(imitateTimeout){
// state = LocalTransactionState.UNKNOW;
// }
log.info("本地事务执行结果:msgKey=" + msgKey + ",execute state:" + state);
return state;
}
/**
* 回查本地事务接口
* 当LocalTransactionState.UNKNOW 会进行回查本地事务接口
* @param messageExt 通过获取 transactionId 来判断这条消息的本地事务执行状态
* @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt){
log.info("调用回查本地事务接口:msgKey=" + new String(messageExt.getBody()));
String msgKey = new String(messageExt.getBody());
SysLogEntity sysLogEntity = JSONObject.parseObject(msgKey, SysLogEntity.class);
// 备注:此处应使用唯一ID查询本地事务是否执行成功,唯一ID可以使用事务的 transactionId。但为了验证方便,只查询DB的订单表是否存在对应的记录
// TransactionDao.isExistTx(transactionId)
List<SysLogEntity> list = sysLogDao.selectList(new QueryWrapper<SysLogEntity>());
LocalTransactionState state = list.size() > 0 ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;
log.info("调用回查本地事务接口的执行结果:" + state);
return state;
}
}
3.客户端发送消息
@RequestMapping("/test1")
public boolean test1(){
String topic = "topic1";
String tag = "reduce";
SysLogEntity sysLogEntity = new SysLogEntity();
sysLogEntity.setUsername("测试test3");
// 设置监听器,此处如果使用MQ其他版本,可能导致强转异常
((TransactionMQProducer) rocketMQTemplate.getProducer()).setTransactionListener(orderTransactionListener);
//构建消息体
String msg = JSONObject.toJSONString(sysLogEntity);
org.springframework.messaging.Message<String> message = MessageBuilder.withPayload(msg).build();
//发送事务消息,由消费者进行进行减少库存
TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(topic + ":" + tag , message, null);
logger.info("Send transaction msg result: " + sendResult);
return sendResult.getSendStatus() == SendStatus.SEND_OK;
}
4.消费端消费
/**
* 消费端
* topic
* consumerGroup 消费者组
*/
@Component
@RocketMQMessageListener(topic = MQConst.APPLY_AUDIT_FIRST_LOAN_TOPIC, consumerGroup = "huangjialin123")
public class ApplyAuditFirstLoanMqConsumer2 implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt){
//消费消息
String message = new String(messageExt.getBody(), StandardCharsets.UTF_8);
consumerCommonServer.applyAuditMqConsumer(message,businessType,1);
}
}
4.RocketMQ处理事务的局限性
1、Rocketmq考虑的是数据最终一致性。上游服务提交之后,下游服务最终只能成功,做不到回滚上游数据。
2、创建订单➕扣减库存,比如producer端是订单的创建,创建好发送消息到库存服务,库存扣减,但是库存为0扣减失败。这个时候RocketMQ是不支持数据TCC回滚的。针对这样的情况可以考虑使用阿里的Seata