【分布式事务解决方案】事务消息

事务消息是RocketMQ区别于其他消息队列的一个很明显的特征。

概念介绍
  • 事务消息:消息队列RocketMQ提供了类似X/Open XA的分布式事务功能,通过事务消息达到分布式事务的最终一致
  • 半事务消息:暂不能投递的消息,发送方已经成功地将消息发送到了RocketMQ服务端,但是服务端未收到生产者对该消息的二次确认,此时该消息被标记为"暂不能投递"状态,处在该状态的消息即称为半事务消息。处在该状态的消息是不能被消费者发现并消费的
  • 消息回查:由于网络、生产者应有重启等原因,导致某条事务消息的二次确认丢失,RocketMQ服务端通过扫描发现某条消息长期处于"半事务消息"状态,需要主动向生产者询问该消息的最终状态(commit或者rollback)。
流程

在这里插入图片描述
事务消息发送步骤:

  1. 发送方将半事务消息发送至服务端
  2. 服务端将消息持久化之后,想发送方返回ACK确认消息已经发送成功,此时消息仍是半事务消息
  3. 发送方执行本地事务逻辑
  4. 发送方根据本地事务执行结果向服务端提交二次确认,服务端收到commit状态则将半事务消息标记为可投递,消费者最终可以收到该消息;服务端收到rollback消息则删除半事务消息,消费者不会接收该消息
实践
<dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.5.2</version>
 </dependency>         

使用事务消息时,必须要实现TransactionListener,这可以实现消息回查功能

依然以前面用户下单成功扣库存为例 展示事务消息的实现

@Service
public class TransactionProducer {
	//事务消息生产者
    private TransactionMQProducer transactionMQProducer;
    private ExecutorService executorService;

    @Autowired
    private TransactionListenerImpl transactionListener;

    @PostConstruct
    public void init() throws MQClientException {
        transactionMQProducer = new TransactionMQProducer();
        transactionMQProducer.setProducerGroup(MqConfig.RPODUCER_GROUP);
        transactionMQProducer.setNamesrvAddr(MqConfig.NAME_SRV_ADDR);

        executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });

        transactionMQProducer.setExecutorService(executorService);
        transactionMQProducer.setTransactionListener(transactionListener);
        transactionMQProducer.start();
    }
	//1、发送事务消息
    public boolean sendMessage(Message message) throws MQClientException {
        TransactionSendResult transactionSendResult = transactionMQProducer.sendMessageInTransaction(message, null);
        System.out.println("message id," + transactionSendResult.getMsgId());
    //2、服务端响应事务消息发送是否成功
        return true;
    }

处理事务回查、事务二次确认的类

@Service
public class TransactionListenerImpl implements TransactionListener {

    @Autowired
    private OrderService orderService;

    private ConcurrentHashMap<String, Boolean> localTrans = new ConcurrentHashMap<>(16);

    @Override
    @Transactional
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            //3、执行本地事务
            orderService.createOrder();
            //message的key对于一条消息而言是唯一的
            //这里标记创建订单的执行结果
            localTrans.put(msg.getKeys(), true);
        } catch (Exception e) {
        //出现异常 标记创建订单的执行结果
            localTrans.put(msg.getKeys(), false);
            throw e;
        }
		//如果返回的状态是Commit/rollback,则不会再消息回查 直接会投递消息或丢弃消息     
		//这里返回UNKNOW 希望消息回查 本地事务的执行情况
		//4、响应本地事务执行情况
        return LocalTransactionState.UNKNOW;
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
       //5&6、消息回查,最终给出本地事务执行情况,commit或rollback
        Boolean b = localTrans.get(msg.getKeys());
        return b ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;
    }
}

消费者:

@Service
public class TransactionConsumer {

    private DefaultMQPushConsumer defaultMQPushConsumer;
	
	//这个里面是实际消费消息的地方
    @Autowired
    private MessageListener messageListener;
	
	@Value("${topic:topic4})
	private String topic;
    
    @Value("${tag:tagA})
    private String tag;

    @PostConstruct
    public void init() throws MQClientException {
        defaultMQPushConsumer = new DefaultMQPushConsumer();
        defaultMQPushConsumer.setConsumerGroup(MqConfig.CONSUMER_GROUP);
        defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        defaultMQPushConsumer.setNamesrvAddr(MqConfig.NAME_SRV_ADDR);
        
        defaultMQPushConsumer.subscribe(topic, tag);
        defaultMQPushConsumer.registerMessageListener(messageListener);
        defaultMQPushConsumer.start();
    }

MessageListener

@Component
public class MessageListener implements MessageListenerConcurrently {

	@Autowired
	private WarehouseService warehouseService

    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
    	//处理扣减库存逻辑
    	String body=new String(msg.getBodys());
    	warehouseService.reduceProductQuantity();
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
}

如果在消息消费的过程中抛出错误,消费者会触发RECONSUME_LATER状态,会重试。所以如果消费者业务有问题,及时更正之后,消息队列重试时依然会得到正常的执行处理,从而保证数据的一致性

MqConfig只是常量的类,涉及的消费者、生产者组 可根据实际情况自己来,至于NameServer地址则需要依赖自己搭建的RocketMQ来定,这里是localhost:9876。所以未给出配置

这里在SpringBoot中启动生产者

@SpringBootApplication
public class WebDemoApplication {


    public static void main(String[] args) throws MQClientException, UnsupportedEncodingException {
        ConfigurableApplicationContext run = SpringApplication.run(WebDemoApplication.class, args);

        TransactionProducer transactionProducer = run.getBean(TransactionProducer.class);
        Message message = new Message("topic4", "tagA", "wojiushowo", "test".getBytes(RemotingHelper.DEFAULT_CHARSET));
        transactionProducer.sendMessage(message);
    }

}

至此一个事务消息的实践即完成。

事务消息的三种状态
  • ROLLBACK_MESSAGE 回滚事务
  • COMMIT_MESSAGE 提交事务
  • UNKNOW 服务端会定时回查生产者消息状态,直到彻底成功或失败

executeLocalTransaction方法返回ROLLBACK_MESSAGE时,表示直接回滚事务,当返回COMMIT_MESSAGE,表示提交事务

当返回UNKNOW时,服务端会在一段时间之后回查checkLocalTransaction,根据checkLocalTransaction返回状态执行事务的操作(回滚或提交)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值