分佈式事務之 RocketMQ 事務消息詳解

事務消息是RocketMQ提供的非常重要的一個特性,在4.x版本之後開源,可以利用事務消息輕鬆地實現分佈式事務。本文對RocketMQ的事務消息進行詳細介紹,並給出了代碼示例。
一. 相關概念
RocketMQ在其消息定義的基礎上,對事務消息擴展了兩個相關的概念:

Half(Prepare) Message——半消息(預處理消息)

半消息是一種特殊的消息類型,該狀態的消息暫時不能被Consumer消費。當一條事務消息被成功投遞到Broker上,但是Broker並沒有接收到Producer發出的二次確認時,該事務消息就處於"暫時不可被消費"狀態,該狀態的事務消息被稱爲半消息。

Message Status Check——消息狀態回查

由於網絡抖動、Producer重啓等原因,可能導致Producer向Broker發送的二次確認消息沒有成功送達。如果Broker檢測到某條事務消息長時間處於半消息狀態,則會主動向Producer端發起回查操作,查詢該事務消息在Producer端的事務狀態(Commit 或 Rollback)。可以看出,Message Status Check主要用來解決分佈式事務中的超時問題。
二. 執行流程

上面是官網提供的事務消息執行流程圖,下面對具體流程進行分析:

Step1:Producer向Broker端發送Half Message;
Step2:Broker ACK,Half Message發送成功;
Step3:Producer執行本地事務;
Step4:本地事務完畢,根據事務的狀態,Producer向Broker發送二次確認消息,確認該Half Message的Commit或者Rollback狀態。Broker收到二次確認消息後,對於Commit狀態,則直接發送到Consumer端執行消費邏輯,而對於Rollback則直接標記爲失敗,一段時間後清除,並不會發給Consumer。正常情況下,到此分佈式事務已經完成,剩下要處理的就是超時問題,即一段時間後Broker仍沒有收到Producer的二次確認消息;
Step5:針對超時狀態,Broker主動向Producer發起消息回查;
Step6:Producer處理回查消息,返回對應的本地事務的執行結果;
Step7:Broker針對回查消息的結果,執行Commit或Rollback操作,同Step4。

三. 代碼實例
本節通過一個簡單的場景模擬RocketMQ的事務消息:存在2個微服務,分別是訂單服務和商品服務。訂單服務進行下單處理,併發送消息給商品服務,對於下單成功的商品進行減庫存。
首先是訂單服務:
/**

  • @Auther: ZhangShenao

  • @Date: 2019/3/27 16:44

  • @Description:使用RocketMQ事務消息——訂單服務發送事務消息,然後進行本地下單,並通知商品服務減庫存
    */
    public class OrderService {
    public static void main(String[] args) throws Exception {
    TransactionMQProducer producer = new TransactionMQProducer();
    producer.setNamesrvAddr(RocketMQConstants.NAMESRV_ADDR);
    producer.setProducerGroup(RocketMQConstants.TRANSACTION_PRODUCER_GROUP);

    //自定義線程池,執行事務操作
    ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 50, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20), (Runnable r) -> new Thread(“Order Transaction Massage Thread”));
    producer.setExecutorService(executor);

    //設置事務消息監聽器
    producer.setTransactionListener(new OrderTransactionListener());

    producer.start();

    System.err.println(“OrderService Start”);

    for (int i = 0;i < 10;i++){
    String orderId = UUID.randomUUID().toString();
    String payload = "下單,orderId: " + orderId;
    String tags = “Tag”;
    Message message = new Message(RocketMQConstants.TRANSACTION_TOPIC_NAME, tags, orderId, payload.getBytes(RemotingHelper.DEFAULT_CHARSET));

    //發送事務消息
    TransactionSendResult result = producer.sendMessageInTransaction(message, orderId);
    System.err.println("發送事務消息,發送結果: " + result);
    }
    }
    }
    複製代碼

事務消息需要一個TransactionListener,主要進行本地事務的執行和事務回查,代碼如下:

/**

  • @Auther: ZhangShenao
  • @Date: 2019/3/27 16:50
  • @Description:訂單事務消息監聽器
    */
    public class OrderTransactionListener implements TransactionListener {
    private static final Map<String, Boolean> results = new ConcurrentHashMap<>();

@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
String orderId = (String) arg;

//記錄本地事務執行結果
boolean success = persistTransactionResult(orderId);
System.err.println("訂單服務執行本地事務下單,orderId: " + orderId + ", result: " + success);
return success ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;

}

@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
String orderId = msg.getKeys();
System.err.println("執行事務消息回查,orderId: " + orderId);
return Boolean.TRUE.equals(results.get(orderId)) ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;
}

private boolean persistTransactionResult(String orderId) {
boolean success = Math.abs(Objects.hash(orderId)) % 2 == 0;
results.put(orderId, success);
return success;
}
}
複製代碼

下面是商品服務及監聽器:
/**

  • @Auther: ZhangShenao
  • @Date: 2019/3/27 17:09
  • @Description:使用RocketMQ事務消息——商品服務接收下單的事務消息,如果消息成功commit則本地減庫存
    /
    public class ProductService {
    public static void main(String[] args) throws Exception {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
    consumer.setNamesrvAddr(RocketMQConstants.NAMESRV_ADDR);
    consumer.setConsumerGroup(RocketMQConstants.TRANSACTION_CONSUMER_GROUP);
    consumer.subscribe(RocketMQConstants.TRANSACTION_TOPIC_NAME, "
    ");
    consumer.registerMessageListener(new ProductListener());
    consumer.start();
    System.err.println(“ProductService Start”);
    }
    }複製代碼

/**

  • @Auther: ZhangShenao
  • @Date: 2019/3/27 17:14
  • @Description:
    */
    public class ProductListener implements MessageListenerConcurrently {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
    Optional.ofNullable(msgs).orElse(Collections.emptyList()).forEach(m -> {
    String orderId = m.getKeys();
    System.err.println("監聽到下單消息,orderId: " + orderId + “, 商品服務減庫存”);
    });
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
    }
    複製代碼

分別運行OrderService和ProductService,可以看出只有事務執行成功的訂單纔會通知商品服務進行減庫存。

監聽到下單消息,orderId: f25a7127-307e-45ce-8f83-6e0a922ebb94, 商品服務減庫存
監聽到下單消息,orderId: d960171d-97c0-4e13-aa4a-c2b96102de4b, 商品服務減庫存
監聽到下單消息,orderId: 63aedaa2-ce74-4cb7-bf58-fb6a73082a73, 商品服務減庫存
監聽到下單消息,orderId: 25764461-70b2-44db-8296-960211179e6e, 商品服務減庫存
監聽到下單消息,orderId: fb319fe7-c8be-4edf-ae4e-6108898068ca, 商品服務減庫存
監聽到下單消息,orderId: 4f61a61a-7254-458a-bc10-9d4006a9f581, 商品服務減庫存

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章