经常会碰到这样的问题,rocketmq消息发送成功了,但是事务提交失败了,这是因为业务事务和rocketmq消息不在一个事务内。
rocketmq在4.3.2版本引入了事务消息,大概的实现思路为:
1、发送一个prepare状态的消息,该消息实际是另外一个half主题,并非发送到真正的主题。所以对消费者是不可见的
2、执行本地的事务方法,并返回执行状态
3、根据事务状态判定消息提交、回滚、未知待核查
3.1、如果是提交状态,则将消息发送到真正的主题
3.2、如果是回滚状态,则将half消息标记为删除
3.3、如果是未知状态,则定时回查事务是否提交(定时器每分钟执行一次;执行时消息必须间隔>6s才能查询;最多检查15次)
如图:
从图中可知,rocketmq是灵活运用了自己的功能,事务通过一个中间的主题来实现;定时核查状态,通过自定义一个消费者,通过消费者的进度来控制,保障消息一定会执行成功后,才会执行下一个消息。
有了大概的思路代码都还是比较简单理解的,这里只列出关键的代码位置:
1、消息发送的代码:DefaultMqProducerImpl.sendMessageInTransaction()
2、服务端Prepared消息接收代码:SendMessageProcessor
2、服务端处理事务结束的处理器:EndTransactionProcessor
3、事务核查定时器定时器:TransactionalMessageCheckService
另外,附上DEMO:
package org.apache.rocketmq.client.transaction;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
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;
public class TransactionListenerImpl implements TransactionListener {
private AtomicInteger transactionIndex = new AtomicInteger(0);
private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<String, Integer>();
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
int value = transactionIndex.getAndIncrement();
int status = value % 3;
localTrans.put(msg.getTransactionId(), status);
System.out.println("本地事务执行:" + msg);
return LocalTransactionState.UNKNOW;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
Integer status = localTrans.get(msg.getTransactionId());
System.out.println(status + ",事务检测:" + msg);
if (null != status) {
switch (status) {
case 0:
return LocalTransactionState.UNKNOW;
case 1:
return LocalTransactionState.COMMIT_MESSAGE;
case 2:
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
return LocalTransactionState.COMMIT_MESSAGE;
}
}
package org.apache.rocketmq.client.transaction;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
public class TransactionProducer {
public static void main(String[] args) throws MQClientException, InterruptedException {
TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer("pid_tx_producer");
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);
producer.setTransactionListener(transactionListener);
producer.setNamesrvAddr("localhost:9876");
producer.start();
for (int i = 0; i < 3; i++) {
try {
Message msg =
new Message("txTopic", null, "KEY" + i,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
System.out.printf("%s%n", sendResult);
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
for (int i = 0; i < 100000; i++) {
Thread.sleep(1000);
}
producer.shutdown();
}
}