概述
RocketMQ和其他消息中间件最大的一个区别是支持了事务消息,这也是分布式事务里面的基于消息的最终一致性方案。
Apache RocketMQ在4.3.0版中已经支持分布式事务消息,这里RocketMQ采用了2PC的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息。

流程如上图所示:
- 生产者向broker发送消息,此时发送的消息对consumer是不可见状态,也就是发送后,consumer不能消费该消息。
- broker向生产者发送确认消息。
- 执行本地事务,比如注册用户。
- 根据本地事务的执行情况选择提交或者回滚消息。
补偿:
- 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”。
- Producer收到回查消息,检查回查消息对应的本地事务的状态
- 根据本地事务状态,重新Commit或者Rollback。
其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。也就是第四步由于某些原因,比如宕机等,导致第四步没有执行。这个补偿机制是由同一生产者组的其他可用生产者来完成。
需要两个角色:
- 事务消息消息生产者
- TransactionListener 实现该接口,定义本地事务逻辑以及回查逻辑。
例子:
流程如下,我只是把本地事务逻辑修改了。

public class LocalTransactionException extends Exception {
}
public class TransactionListenerDemo implements TransactionListener{
private AtomicInteger num = new AtomicInteger();
//这个是记录本地事务执行结果为UNKNOW的情况下的状况用于回查,实际上要用数据库表来记录,这里就简单点用一个map来记录。
private Map<String,Boolean> checkMap = new ConcurrentHashMap<>();
//执行本地事务的地方,返回本地事务执行结果,有三种状态
//COMMIT_MESSAGE 执行成功,提交消息
//ROLLBACK_MESSAGE 执行失败,回滚消息
//UNKNOW 中间状态,该状态的消息要进行回查
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
//这里模拟业务操作,value对4取模,0就返回COMMIT_MESSAGE 1 就抛出异常
// 2就返回UNKNOW(如果是2,就往回查表里输入true,表示回查提交,如果是3或者null,那就回查回滚。
try {
//这里就全包在try/catch里面了 实际上只需包会本地事务代码,比如注册用户的sql操作。
int value = num.getAndIncrement();
int mod = value%4;
if (mod == 0){
return LocalTransactionState.COMMIT_MESSAGE;
}else if (mod == 1){
//模拟抛出异常
throw new LocalTransactionException();
}else {
if (mod == 2){
//如果为2,就回查时让其提交
checkMap.put(msg.getTransactionId(),true);
}else{
//否则消息回滚
checkMap.put(msg.getTransactionId(),true);
}
return LocalTransactionState.UNKNOW;
}
}catch (LocalTransactionException ex){
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
//回查方法,本地事务返回UNKNOW会触发。
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
Boolean commitOrNot = checkMap.get(msg.getTransactionId());
if (commitOrNot != null && commitOrNot){
//如果commitOrNot不为空并且为true,提交消息
return LocalTransactionState.COMMIT_MESSAGE;
}else {
//否则回滚消息,为空表示checkMap.put(); 操作和return LocalTransactionState.UNKNOW;没有执行到就宕机的情况
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
}
public class TransactionProducerDemo {
public static void main(String[] args) throws MQClientException, InterruptedException {
//创建一个事务生产者TransactionMQProducer
TransactionMQProducer producer = new TransactionMQProducer("transaction-msg-test");
producer.setNamesrvAddr("192.168.18.142:9876");
//创建一个线程池来执行事务检查
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("client-transaction-msg-check-thread");
return thread;
}
});
producer.setExecutorService(executorService);
//创建事务监听器
TransactionListenerDemo transactionListenerDemo = new TransactionListenerDemo();
producer.setTransactionListener(transactionListenerDemo);
producer.start();
String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
//发送消息
for (int i = 0; i < 10; i++) {
try {
Message msg =
new Message("TopicTestTransation21",
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
Thread.sleep(10);
} catch (MQClientException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 100000; i++) {
Thread.sleep(1000);
}
}
}
结果:

由逻辑可得0、4、8 %4=0 ,属于提交消息,1、5、9 %4=1 属于回滚消息。2、6%4=2 属于回查中提交消息。3、7%4=3,回查中回滚消息,与结果正确。
本文介绍Apache RocketMQ如何通过2PC思想实现事务消息处理,并通过补偿机制确保消息的一致性。文章详细展示了事务消息的实现过程及示例代码。
460

被折叠的 条评论
为什么被折叠?



