RocketMQ中的消息类型

136 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

消息类型

在RocketMQ中,消息被分为了三种类型:

  1. 普通消息
  2. 顺序消息
  3. 事务消息

下面分别来看看RocketMQ中如何处理这三种消息。

普通消息

RocketMQ提供了三种方式来发送普通消息:

  • 可靠同步发送:指消息发送方发出数据后,在收到接收方发回响应之后才进行下一个数据包的发送
  • 可靠异步发送:指消息发送发发出数据后,不等接收方发回响应就进行下一个数据包的发送,发送方通过回调接口接收服务器响应,并对响应结果进行处理
  • 单向发送:指发送方只负责发送消息,不等待接收方响应也没有回调方法触发

\

发送同步消息非常简单,代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SendMessageTypeTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void testSyncSend(){
        // 参数一:topic
        // 参数二:消息内容
        // 参数三:超时时间
        SendResult result = rocketMQTemplate.syncSend("myTopic1", "testSyncSend", 10000);
        System.out.println(result);
    }
}

若是发布异步消息,只需修改一下调用方法即可:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SendMessageTypeTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void testAsyncSend() throws InterruptedException {
        // 参数一:topic
        // 参数二:消息内容
        // 参数三:回调
        rocketMQTemplate.asyncSend("myTopic2", "testAsyncSend", new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println(sendResult);
            }

            @Override
            public void onException(Throwable throwable) {
                System.out.println(throwable);
            }
        });
        Thread.sleep(300000);
    }
}

发送异步消息的时候要注意,因为它是基于回调的形式处理消息结果的,所以我们需要对主线程进行休眠,让其一直等待回调方法被执行,否则主线程会立马执行结束,回调方法也就无法被调用了。

\

最后是单向发送消息:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SendMessageTypeTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void testOneWay() {
        rocketMQTemplate.sendOneWay("myTopic3","testOneWay");
    }
}

顺序消息

顺序消息是消息队列提供的一种严格按照顺序来发布和消费的消息类型,我们知道,RocketMQ中的Broker将消息存储在MessageQueue中,若是现在发送三条消息,这三条消息分别被存储在三个不同的MessageQueue中,该如何保证在消费这些消息时的顺序与发布时的顺序一致呢?这就需要借助顺序消息了。

@RunWith(SpringRunner.class)
@SpringBootTest
public class SendMessageTypeTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void testOneWayOrderly() {
        rocketMQTemplate.sendOneWayOrderly("myTopic","testOneWayOrderly","a");
    }
}

只需要调用sendOneWayOrderly方法即可,该方法需要接收一个特殊的参数,即hashKey,RocketMQ正是通过它来决定消息该被发送到哪个MessageQueue中,值可以任意填写,只需要有所区分即可。

事务消息

事务消息是支持事务的消息,通过事务消息,我们就能够实现分布式事务的最终一致性。

\

事务消息的流程如下:

\

首先消息生产者发送事务消息给RocketMQ,RocketMQ会进行响应,当接收到响应后,生产者会调用本地的事务方法,调用本地事务方法有两种结果,成功和失败,成功返回COMMIT,失败返回ROLLBACK。

若是遇到网络等问题导致事务消息的二次确认丢失,RocketMQ会进行回查,即查询本地事务的状态,然后继续进行相关操作。

\

这里我们模拟一个下单业务,首先编写发送事务消息代码:

public void createOrderBefore(Order order) {
    String txId = UUID.randomUUID().toString();
    // 发送事务消息
    rocketMQTemplate.sendMessageInTransaction(
        "tx-producer-group",
        "tx-topic",
        MessageBuilder.withPayload(order).setHeader("txId", txId).build(),
        order
    );
}

然后需要编写一个接口实现RocketMQLocalTransactionListener:

@Service
@RocketMQTransactionListener(txProducerGroup = "tx-producer-group")
public class OrderServiceImplListener implements RocketMQLocalTransactionListener {

    @Autowired
    private OrderService orderService;
    @Autowired
    private TxLogMapper txLogMapper;

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        String txId = (String) message.getHeaders().get("txId");
        try {
            // 执行本地事务
            Order order = (Order) o;
            orderService.createOrder(order, txId);
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        // 消息回查
        String txId = (String) message.getHeaders().get("txId");
        TxLog txLog = txLogMapper.selectById(txId);
        if (txLog != null) {
            // 本地事务执行成功
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

该接口需要实现两个方法,分别是对事务方法的处理和消息回查的逻辑,在事务处理方法中,我们调用本地事务即可,本地事务代码如下:

@Transactional
@Override
public void createOrder(Order order, String txId) {
    orderMapper.insert(order);
    TxLog txLog = new TxLog();
    txLog.setTxId(txId);
    txLog.setDate(new Date());
    txLogMapper.insert(txLog);
}

这里的消息回查是基于一张数据表实现的,所以创建Bean:

@Data
@TableName("shop_txlog")
public class TxLog {

    @TableId(type = IdType.AUTO)
    private String txId;
    private Date date;
}

因为订单和该数据是在同一个事务方法中,它们俩会同时成功或失败,所以可以通过查询指定订单id是否在该表中存在数据来判断事务方法是否执行成功,最后调用 createOrderBefore 方法进行测试即可。