前言
最近有看分布式的事务相关的内容。之前有配置过XA模式的分布式事务。然后又刚好看了有关rocketmq的最终一致性的相关方案。决定实践一下
逻辑实现
1、A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
2、如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
3、如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
4、mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
核心逻辑
添加TransactionListener的继承类,实现executeLocalTransaction(执行事务方法),和checkLocalTransaction(回调检查方法)。添加到TransactionMQProducer中。并且还要添加ExecutorService(用于产生回调执行的线程池)
代码实现
rocketmq监听回调类
用于配置事务执行和事务问题的回调
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 java.io.UnsupportedEncodingException;
@Slf4j
public class AffairsMsgListener implements TransactionListener {
/**
* COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
*
* @param msg
* @param o
* @return
*/
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object o) {
log.info("回调方法,message:{}", msg);
String msgBody;
//执行本地业务的时候,再插入一条数据到事务表中,供checkLocalTransaction进行check使用,避免doBusinessCommit业务成功,但是未返回Commit
try {
msgBody = new String(msg.getBody(), "utf-8");
String key = msg.getKeys();
if (key.contains("fail")) {
doBusinessfail(key, msgBody);
return LocalTransactionState.ROLLBACK_MESSAGE;
} else if (key.contains("unknown")) {
doBusinessNuknow(key, msgBody);
return LocalTransactionState.UNKNOW;
} else {
doBusinessCommit(key, msgBody);
return LocalTransactionState.COMMIT_MESSAGE;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return LocalTransactionState.ROLLBACK_MESSAGE;
} catch (Exception e) {
e.printStackTrace();
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
log.info("检查方法,MessageExt:{}", msg);
Boolean result = checkBusinessStatus(msg.getKeys());
if (result) {
return LocalTransactionState.COMMIT_MESSAGE;
} else {
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
public static void doBusinessCommit(String messageKey, String msgbody) {
log.info("insert 事务消息到本地消息表中,消息执行成功,messageKey为:" + messageKey);
}
public static void doBusinessfail(String messageKey, String msgbody) {
log.info("insert 事务消息到本地消息表中,消息执行失败!messageKey为:" + messageKey);
}
public static void doBusinessNuknow(String messageKey, String msgbody) {
log.info("insert 事务消息到本地消息表中,消息执行无响应!messageKey为:" + messageKey);
}
public static Boolean checkBusinessStatus(String messageKey) {
if (messageKey.contains("unknownSuccess")) {
log.info("查询数据库 messageKey为" + messageKey + "的消息已经消费成功了,可以提交消息");
return true;
}
if(messageKey.contains("unknownFail")) {
log.info("查询数据库 messageKey为" + messageKey + "的消息不存在或者未消费成功了,可以回滚消息");
return false;
}
return true;
}
public static void main(String[] args) {
String a = "tru2e1";
System.out.println(a.contains("true"));
}
}
bean配置
@Bean(name = "producer2")
public TransactionMQProducer getRocketMQProducer2() {
TransactionMQProducer producer;
producer = new TransactionMQProducer("groupName2");
producer.setNamesrvAddr("namesrvAddr");
//如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
//producer.setInstanceName("instanceName");
producer.setMaxMessageSize(4096);
producer.setSendMsgTimeout(3000);
//如果发送消息失败,设置重试次数,默认为2次
producer.setRetryTimesWhenSendFailed(2);
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(new AffairsMsgListener());
try {
producer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
return producer;
}
controller使用
@Resource(name = "producer2")
private TransactionMQProducer transactionMQProducer;
@Override
public Result<String> sendOther(String key,String msgInfo) {
// 可以不使用Config中的Group
//transactionMQProducer.setProducerGroup(paramConfigService.rocketGroup2);
SendResult sendResult = null;
try {
Message sendMsg = new Message(paramConfigService.rocketTopic2,
paramConfigService.rocketTag2,
key, msgInfo.getBytes());
sendResult = transactionMQProducer.sendMessageInTransaction(sendMsg,null);
} catch (Exception e) {
e.printStackTrace();
}
log.info("sendResult:{}", new Gson().toJson(sendResult));
return Result.success(sendResult.getMsgId());
}
结果值
正常事务执行完毕发送mq消息
2020-10-29 20:11:45.207 INFO 10057 --- [nio-8761-exec-3] com.chen.common.logAop.ParamsLog2Aspect : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,RequestArgs:["test1","hello"]
2020-10-29 20:11:45.237 INFO 10057 --- [nio-8761-exec-3] com.chen.common.mq.AffairsMsgListener : 回调方法,message:Message{topic='mytopic2', flag=0, properties={KEYS=test1, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948774B70001, WAIT=true, PGROUP=chengroup2, TAGS=rocketTag2}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948774B70001'}
2020-10-29 20:11:45.238 INFO 10057 --- [nio-8761-exec-3] com.chen.common.mq.AffairsMsgListener : insert 事务消息到本地消息表中,消息执行成功,messageKey为:test1
2020-10-29 20:11:45.248 INFO 10057 --- [nio-8761-exec-3] c.c.c.service.impl.RocketMqServiceImpl : sendResult:{"localTransactionState":"COMMIT_MESSAGE","sendStatus":"SEND_OK","msgId":"C0A8041E274918B4AAC2948774B70001","messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":12},"queueOffset":15,"traceOn":true}
2020-10-29 20:11:45.251 INFO 10057 --- [nio-8761-exec-3] com.chen.common.logAop.ParamsLog2Aspect : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,ResponseArgs:{"code":"200","msg":"成功","data":"C0A8041E274918B4AAC2948774B70001"},Time-Consuming:45ms
2020-10-29 20:11:45.304 INFO 10057 --- [MessageThread_1] com.chen.common.logAop.ParamsLogAspect : ClassMethod:com.chen.core.mqListener.RocketMsgListener.consumeMessage,RequestArgs:[[{"queueId":12,"storeSize":247,"queueOffset":3,"sysFlag":8,"bornTimestamp":1603973505207,"bornHost":{},"storeTimestamp":1603973505261,"storeHost":{},"msgId":"81D347B900002A9F000000000000ECED","commitLogOffset":60653,"bodyCRC":907060870,"reconsumeTimes":0,"preparedTransactionOffset":60390,"topic":"mytopic2","flag":0,"properties":{"MIN_OFFSET":"0","REAL_TOPIC":"mytopic2","MAX_OFFSET":"4","KEYS":"test1","TRAN_MSG":"true","CONSUME_START_TIME":"1603973505282","UNIQ_KEY":"C0A8041E274918B4AAC2948774B70001","WAIT":"true","PGROUP":"chengroup2","TAGS":"rocketTag2","REAL_QID":"12"},"body":[104,101,108,108,111],"transactionId":"C0A8041E274918B4AAC2948774B70001"}],{"messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":12},"delayLevelWhenNextConsume":0,"ackIndex":2147483647}]
2020-10-29 20:11:45.309 INFO 10057 --- [MessageThread_1] c.c.core.mqListener.RocketMsgListener : 接受到的消息为:hello
2020-10-29 20:11:45.310 INFO 10057 --- [MessageThread_1] com.chen.common.logAop.ParamsLogAspect : ClassMethod:com.chen.core.mqListener.RocketMsgListener.consumeMessage,ResponseArgs:"CONSUME_SUCCESS",Time-Consuming:26ms
本地事务执行失败,消息不发送
2020-10-29 20:12:41.971 INFO 10057 --- [nio-8761-exec-4] com.chen.common.logAop.ParamsLog2Aspect : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,RequestArgs:["fail","hello"]
2020-10-29 20:12:41.989 INFO 10057 --- [nio-8761-exec-4] com.chen.common.mq.AffairsMsgListener : 回调方法,message:Message{topic='mytopic2', flag=0, properties={KEYS=fail, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948852730002, WAIT=true, PGROUP=chengroup2, TAGS=rocketTag2}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948852730002'}
2020-10-29 20:12:41.989 INFO 10057 --- [nio-8761-exec-4] com.chen.common.mq.AffairsMsgListener : insert 事务消息到本地消息表中,消息执行失败!messageKey为:fail
2020-10-29 20:12:41.992 INFO 10057 --- [nio-8761-exec-4] c.c.c.service.impl.RocketMqServiceImpl : sendResult:{"localTransactionState":"ROLLBACK_MESSAGE","sendStatus":"SEND_OK","msgId":"C0A8041E274918B4AAC2948852730002","messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":15},"queueOffset":16,"traceOn":true}
2020-10-29 20:12:41.992 INFO 10057 --- [nio-8761-exec-4] com.chen.common.logAop.ParamsLog2Aspect : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,ResponseArgs:{"code":"200","msg":"成功","data":"C0A8041E274918B4AAC2948852730002"},Time-Consuming:21ms
本息事务执行条件未知,回调后发现事务成功
2020-10-29 20:13:27.162 INFO 10057 --- [nio-8761-exec-5] com.chen.common.logAop.ParamsLog2Aspect : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,RequestArgs:["unknownSuccess","hello"]
2020-10-29 20:13:27.177 INFO 10057 --- [nio-8761-exec-5] com.chen.common.mq.AffairsMsgListener : 回调方法,message:Message{topic='mytopic2', flag=0, properties={KEYS=unknownSuccess, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948902FA0003, WAIT=true, PGROUP=chengroup2, TAGS=rocketTag2}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948902FA0003'}
2020-10-29 20:13:27.177 INFO 10057 --- [nio-8761-exec-5] com.chen.common.mq.AffairsMsgListener : insert 事务消息到本地消息表中,消息执行无响应!messageKey为:unknownSuccess
2020-10-29 20:13:27.178 INFO 10057 --- [nio-8761-exec-5] c.c.c.service.impl.RocketMqServiceImpl : sendResult:{"localTransactionState":"UNKNOW","sendStatus":"SEND_OK","msgId":"C0A8041E274918B4AAC2948902FA0003","messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":6},"queueOffset":17,"traceOn":true}
2020-10-29 20:13:27.178 INFO 10057 --- [nio-8761-exec-5] com.chen.common.logAop.ParamsLog2Aspect : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,ResponseArgs:{"code":"200","msg":"成功","data":"C0A8041E274918B4AAC2948902FA0003"},Time-Consuming:17ms
2020-10-29 20:14:21.156 INFO 10057 --- [sg-check-thread] com.chen.common.mq.AffairsMsgListener : 检查方法,MessageExt:MessageExt [queueId=6, storeSize=282, queueOffset=18, sysFlag=0, bornTimestamp=1603973607162, bornHost=/122.224.137.218:51509, storeTimestamp=1603973607171, storeHost=/129.211.71.185:10911, msgId=81D347B900002A9F000000000000F10B, commitLogOffset=61707, bodyCRC=907060870, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='mytopic2', flag=0, properties={REAL_TOPIC=mytopic2, TRANSACTION_CHECK_TIMES=1, KEYS=unknownSuccess, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948902FA0003, WAIT=false, PGROUP=chengroup2, TAGS=rocketTag2, REAL_QID=6}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948902FA0003'}]
2020-10-29 20:14:21.157 INFO 10057 --- [sg-check-thread] com.chen.common.mq.AffairsMsgListener : 查询数据库 messageKey为unknownSuccess的消息已经消费成功了,可以提交消息
2020-10-29 20:14:21.189 INFO 10057 --- [MessageThread_2] com.chen.common.logAop.ParamsLogAspect : ClassMethod:com.chen.core.mqListener.RocketMsgListener.consumeMessage,RequestArgs:[[{"queueId":6,"storeSize":281,"queueOffset":4,"sysFlag":8,"bornTimestamp":1603973607162,"bornHost":{},"storeTimestamp":1603973661168,"storeHost":{},"msgId":"81D347B900002A9F000000000000F234","commitLogOffset":62004,"bodyCRC":907060870,"reconsumeTimes":0,"preparedTransactionOffset":61707,"topic":"mytopic2","flag":0,"properties":{"MIN_OFFSET":"0","REAL_TOPIC":"mytopic2","TRANSACTION_CHECK_TIMES":"1","MAX_OFFSET":"5","KEYS":"unknownSuccess","TRAN_MSG":"true","CONSUME_START_TIME":"1603973661187","UNIQ_KEY":"C0A8041E274918B4AAC2948902FA0003","WAIT":"true","PGROUP":"chengroup2","TAGS":"rocketTag2","REAL_QID":"6"},"body":[104,101,108,108,111],"transactionId":"C0A8041E274918B4AAC2948902FA0003"}],{"messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":6},"delayLevelWhenNextConsume":0,"ackIndex":2147483647}]
2020-10-29 20:14:21.190 INFO 10057 --- [MessageThread_2] c.c.core.mqListener.RocketMsgListener : 接受到的消息为:hello
2020-10-29 20:14:21.190 INFO 10057 --- [MessageThread_2] com.chen.common.logAop.ParamsLogAspect : ClassMethod:com.chen.core.mqListener.RocketMsgListener.consumeMessage,ResponseArgs:"CONSUME_SUCCESS",Time-Consuming:3ms
本息事务执行条件未知,回调后发现事务失败
2020-10-29 20:15:21.643 INFO 10057 --- [nio-8761-exec-8] com.chen.common.logAop.ParamsLog2Aspect : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,RequestArgs:["unknownFail","hello"]
2020-10-29 20:15:21.653 INFO 10057 --- [nio-8761-exec-8] com.chen.common.mq.AffairsMsgListener : 回调方法,message:Message{topic='mytopic2', flag=0, properties={KEYS=unknownFail, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948AC22B0004, WAIT=true, PGROUP=chengroup2, TAGS=rocketTag2}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948AC22B0004'}
2020-10-29 20:15:21.654 INFO 10057 --- [nio-8761-exec-8] com.chen.common.mq.AffairsMsgListener : insert 事务消息到本地消息表中,消息执行无响应!messageKey为:unknownFail
2020-10-29 20:15:21.654 INFO 10057 --- [nio-8761-exec-8] c.c.c.service.impl.RocketMqServiceImpl : sendResult:{"localTransactionState":"UNKNOW","sendStatus":"SEND_OK","msgId":"C0A8041E274918B4AAC2948AC22B0004","messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":3},"queueOffset":19,"traceOn":true}
2020-10-29 20:15:21.654 INFO 10057 --- [nio-8761-exec-8] com.chen.common.logAop.ParamsLog2Aspect : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,ResponseArgs:{"code":"200","msg":"成功","data":"C0A8041E274918B4AAC2948AC22B0004"},Time-Consuming:12ms
2020-10-29 20:16:21.207 INFO 10057 --- [sg-check-thread] com.chen.common.mq.AffairsMsgListener : 检查方法,MessageExt:MessageExt [queueId=3, storeSize=279, queueOffset=20, sysFlag=0, bornTimestamp=1603973721643, bornHost=/122.224.137.218:51509, storeTimestamp=1603973721652, storeHost=/129.211.71.185:10911, msgId=81D347B900002A9F000000000000F4E2, commitLogOffset=62690, bodyCRC=907060870, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='mytopic2', flag=0, properties={REAL_TOPIC=mytopic2, TRANSACTION_CHECK_TIMES=1, KEYS=unknownFail, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948AC22B0004, WAIT=false, PGROUP=chengroup2, TAGS=rocketTag2, REAL_QID=3}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948AC22B0004'}]
2020-10-29 20:16:21.207 INFO 10057 --- [sg-check-thread] com.chen.common.mq.AffairsMsgListener : 查询数据库 messageKey为unknownFail的消息不存在或者未消费成功了,可以回滚消息
说明
可以看到。当我们的本地事务成功或者回调后发现事务成功了。我们的rocketmq消费者都收到了对应的消息。当事务失败或者回调后发现事务失败了。都回滚了对应的消息。不会再发送对应的mq消息了。
以上就是我们的rocketmq事务相关的内容了。end~