RocketMQ学习之事务消息

86 阅读2分钟

官方Demo

  • Producer 生产者

public class Producer {

public static void main(String[] args) throws Exception {
    DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
    producer.setNamesrvAddr("172.16.124.130:9876");
    producer.start();

    String[] tags = new String[]{"TagA", "TagB", "TagC"};
    List<OrderStep> orderList = new Producer().buildOrders();

    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String dateStr = sdf.format(date);

    for (int i = 0; i < 1; i++) {
        String body = dateStr + " Hello RocketMQ " + orderList.get(i);
        Message msg = new Message("TopicTest", tags[i % tags.length], "KEY" + i, body.getBytes());

        SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
            @Override
            public MessageQueue select(List<MessageQueue> mqs, Message message, Object o) {
               long id = (Long)o;
               long index = id % mqs.size();
               return mqs.get((int)index);
            }
        }, orderList.get(i).getOrderId());

        System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s",
                sendResult.getSendStatus(),
                sendResult.getMessageQueue().getQueueId(),
                body));
    }

    producer.shutdown();
}

/**
 * 订单的步骤
 */
private static class OrderStep {
    private long orderId;
    private String desc;

    public long getOrderId() {
        return orderId;
    }

    public void setOrderId(long orderId) {
        this.orderId = orderId;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "OrderStep{" +
                "orderId=" + orderId +
                ", desc='" + desc + '\'' +
                '}';
    }
}

/**
 * 生成模拟订单数据
 */
private List<OrderStep> buildOrders() {
    List<OrderStep> orderList = new ArrayList<OrderStep>();

    OrderStep orderDemo = new OrderStep();
    orderDemo.setOrderId(15103111039L);
    orderDemo.setDesc("创建");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103111065L);
    orderDemo.setDesc("创建");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103111039L);
    orderDemo.setDesc("付款");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103117235L);
    orderDemo.setDesc("创建");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103111065L);
    orderDemo.setDesc("付款");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103117235L);
    orderDemo.setDesc("付款");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103111065L);
    orderDemo.setDesc("完成");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103111039L);
    orderDemo.setDesc("推送");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103117235L);
    orderDemo.setDesc("完成");
    orderList.add(orderDemo);

    orderDemo = new OrderStep();
    orderDemo.setOrderId(15103111039L);
    orderDemo.setDesc("完成");
    orderList.add(orderDemo);

    return orderList;
}
* Consumer 消费者
```java
public class ConsumerInOrder {
  public static void main(String[] args) throws Exception {
      DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3");
      consumer.setNamesrvAddr("172.16.124.130:9876");
      /**
       * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br>
       * 如果非第一次启动,那么按照上次消费的位置继续消费
       */
      consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

      consumer.subscribe("TopicTest", "TagA || TagB || TagC");

      consumer.registerMessageListener(new MessageListenerOrderly() {

          Random random = new Random();

          @Override
          public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
              context.setAutoCommit(true);
              for (MessageExt msg : msgs) {
                  // 可以看到每个queue有唯一的consume线程来消费, 订单对每个queue(分区)有序
                  System.out.println("consumeThread=" + Thread.currentThread().getName() + "queueId=" + msg.getQueueId() + ", content:" + new String(msg.getBody()));
              }

              try {
                  //模拟业务逻辑处理中...
                  TimeUnit.SECONDS.sleep(random.nextInt(10));
              } catch (Exception e) {
                  e.printStackTrace();
              }
              return ConsumeOrderlyStatus.SUCCESS;
          }
      });

      consumer.start();

      System.out.println("Consumer Started.");
  }
}
  • 结果分析 可见只有1、4、7 消息被消费者消费了,说明其他消费并未被消费。

原理简介

  • RocketMQ 的事务消息采用二阶段提交协议,在发送时,先发送 half message (半消息,这类消息会被进一步封装在 Broker 中,不会被消费者订阅到)。等生产者本地事务处理完成或者失败,发送 commit 或者 rollback 到 Broker,Broker 将 Topic 恢复为原来的 Topic, 或者丢弃消息。但这里有一个问题,如果生产者发送的 commit 或者 rollback 消息丢失,那么这类消息如何处理?Broker 会定期 check 消息状态,从而得知事务是否执行成功。这就是 RocketMQ 事务消息的事务回查机制。

  • 流程图一

  • 流程图二 [注] 以上流程图非本人原创,均来自于互联网。