消息队列的方式 解决分布式事务 demo
rocketMq解决分布式事务
思路
将分布式事务拆分成两个本地事务,对于事务发起方,可理解为生产者,我们只需要保证本地事务和发送消息 这两个动作的一致性,对于事务的另一方可理解为mq 消息的消费者,直需要保证成功消费消息即可。若失败,可重试,可以设定一个重试次数,达到了就人工干预(毕竟这种情况比较小)。
直接上代码:生产者
public class TestTransactionProducer {
private static Logger logger = LoggerFactory.getLogger(TestTransactionProducer.class);
public static void main(String[] args) {
TransactionMQProducer producer = new TransactionMQProducer("transactionProducer");
producer.setNamesrvAddr(MqConfig.NAME_SERVER);
//事物监听
TransactionListener listener = new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
try {
logger.info("开始执行本地事务,也就是自己的业务");
logger.info("如果执行成功,就返回本地事务commit");
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
logger.error("本地事务执行失败。。。。。。。。。。");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
logger.info("自动查本地业务的事务是否成功,一般是手动查询数据库,然后返回 确认或回滚信息");
return LocalTransactionState.COMMIT_MESSAGE;
}
};
//本地事务执行器
producer.setTransactionListener(listener);
try {
//只需要start 一次
producer.start();
//构建消息
Message msg1 = new Message("TransactionTopic", "tag", "KEY1", "hello RocketMQ 4.7R1111".getBytes());
Message msg2 = new Message("TransactionTopic", "tag", "KEY2", "hello RocketMQ 4.7R2222".getBytes());
//发送带有事务性质的消息(发送消息会先执行本地事务,本地成功或失败都会有确认消息返回,)
TransactionSendResult sendResult1 = producer.sendMessageInTransaction(msg1, null);
System.out.println(new Date().getTime() + " msg1发送结果: " + sendResult1);
TransactionSendResult sendResult2 = producer.sendMessageInTransaction(msg2, null);
System.out.println(new Date().getTime() + " msg2结果:" + sendResult2);
} catch (MQClientException e) {
logger.error(e.getErrorMessage());
}
}
}
消费者:
public static void main(String[] args) throws MQClientException {
//声明并初始化一个consumer
//需要一个consumer group名字作为构造方法的参数,这里为consumer1
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer1");
//同样也要设置NameServer地址
consumer.setNamesrvAddr("127.0.0.1:9876");
//这里设置的是一个consumer的消费策略
//CONSUME_FROM_LAST_OFFSET 默认策略,从该队列最尾开始消费,即跳过历史消息
//CONSUME_FROM_FIRST_OFFSET 从队列最开始开始消费,即历史消息(还储存在broker的)全部消费一遍
//CONSUME_FROM_TIMESTAMP 从某个时间点开始消费,和setConsumeTimestamp()配合使用,默认是半个小时以前
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//设置consumer所订阅的Topic和Tag,*代表全部的Tag
// consumer.subscribe("TopicTest", "*");
consumer.subscribe("TransactionTopic", "*");
//设置一个Listener,主要进行消息的逻辑处理
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
//消费之前可以利用redis实现的分布式锁做到不重复消费。
for (int i = 0; i < msgs.size(); i++) {
System.out.println("接收到的消息" + i + ": " + msgs.get(i));
System.out.println("消息体 " + i + ":" + new String(msgs.get(i).getBody()));
}
//返回消费状态
//CONSUME_SUCCESS 消费成功
//RECONSUME_LATER 消费失败,若消费失败,相当于告诉broker 消费失败了,消息你别丢,一会儿我再来消费,自动重试的。
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//调用start()方法启动consumer
consumer.start();
System.out.println("Consumer Started........");
}
mq用的是4.7 版本的,自己瞎琢磨的,这样应该可以实现分布式事务 数据最终一致性的要求,若有不同的看法,欢迎留言