RocketMQ事务消息用来解决本地事务和消息提交的原子性问题。而投递到MQ服务器后,消费者是否能一定消费成功是无法保证的。
通过冯嘉发布的《RocketMQ 4.3正式发布,支持分布式事务》一文可以看到RocketMQ采用了2PC的方案来提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息。
这张图说明了事务消息的大致方案,分为两个逻辑:正常事务消息的发送及提交、事务消息的补偿流程
事务消息发送及提交:
- 发送消息(half消息)
- 服务端响应消息写入结果
- 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
- 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
补偿流程:
- 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
- Producer收到回查消息,检查回查消息对应的本地事务的状态
- 根据本地事务状态,重新Commit或者Rollback
补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。
1、定义事务消息必须实现的接口
/**
* 事务消息必须实现的接口
* @author Larkin
*/
public interface TransactionalMQService {
/**
* 本地使用事务执行
* @param msg
* @return
*/
boolean process(String msg, String transactionId);
/**
* 查询本地事务结果
* @param msg
* @return
*/
boolean checkSuccess(String msg, String transactionId);
}
2、定义AbstractRocketMQListener 抽象类,implements TransactionListener,并实现 executeLocalTransaction 和 checkLocalTransaction 方法,一个是实现本地事务,一个是查询事务结果。
@Component
public abstract class AbstractRocketMQListener implements TransactionListener, ApplicationContextAware {
@Value("${rocketmq.producer.group}")
private String producerGroupName;
private static final String TOPIC = "koala-dev-event-centre";
private static ApplicationContext APPLICATION_CONTEXT;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
APPLICATION_CONTEXT = applicationContext;
}
@Resource
private RocketMQTemplate rocketMQTemplate;
/**
* 执行本地事务 并获取执行结果
*
* @param message
* @param o
* @return
*/
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
String msg = new String(message.getBody());
String transactionId = message.getTransactionId();
System.out.println("------------消息执行本地事务--------------");
try {
// 此处必须通过接口强制转换,否则很可能获取不到代理类
bo