目录
前言
本文主要详细讲解 RocketMQ 的四种消息类型:普通消息、定时或延迟消息、顺序消息、事务消息。每种消息类型包括使用场景、原理机制、优缺点和代码示例。注:部分图片集信息来源于Apache
普通消息(Regular Messages)
普通消息是Apache RocketMQ基本消息功能,支持生产者和消费者的异步解耦通信。
普通消息生命周期
-
初始化:消息被生产者构建并完成初始化,待发送到服务端的状态。
-
待消费:消息被发送到服务端,对消费者可见,等待消费者消费的状态。
-
消费中:消息被消费者获取,并按照消费者本地的业务逻辑进行处理的过程。 此时服务端会等待消费者完成消费并提交消费结果,如果一定时间后没有收到消费者的响应,Apache RocketMQ会对消息进行重试处理。具体信息,请参见消费重试。
-
消费提交:消费者完成消费处理,并向服务端提交消费结果,服务端标记当前消息已经被处理(包括消费成功和失败)。 Apache RocketMQ默认支持保留所有消息,此时消息数据并不会立即被删除,只是逻辑标记已消费。消息在保存时间到期或存储空间不足被删除前,消费者仍然可以回溯消息重新消费。
-
消息删除:Apache RocketMQ按照消息保存机制滚动清理最早的消息数据,将消息从物理文件中删除。更多信息,请参见消息存储和清理机制。
使用限制
普通消息仅支持使用MessageType为Normal主题,即普通消息只能发送至类型为普通消息的主题中,发送的消息的类型必须和主题的类型一致。
使用场景
- 数据集成传输:适用于大多数消息传递需求,如日志收集、通知推送等。
- 微服务异步解耦:如异步任务执行、数据处理、订单下单等。
原理机制
普通消息是 RocketMQ 中最基本的消息类型。生产者将消息发送到指定的主题(Topic),Broker 存储消息,消费者从 Broker 拉取消息进行处理。消息的存储和传递机制如下:
- 消息生产:生产者将消息发送到指定的 Topic 和 Queue 中。每个 Topic 可以有多个 Queue,用于实现并发处理。
- 消息存储:消息存储在 Broker 的文件系统中。消息按照时间顺序存储在日志文件中,文件系统的高效写入和索引机制确保了消息的持久化。
- 消息消费:消费者从 Topic 的 Queue 中拉取消息进行处理。消费者可以是并发处理的,也可以是顺序处理的。
优缺点
- 优点:
- 简单易用:适合一般的消息传递需求,无需复杂配置。
- 高吞吐量:支持大规模的消息生产和消费,性能优越。
- 缺点:
- 消息顺序性:在高并发场景中,可能无法保证消息的顺序。
- 事务支持:不支持复杂的事务管理,无法保证跨系统的一致性。
代码示例
生产者发送普通消息:
DefaultMQProducer producer = new DefaultMQProducer("regular_producer_group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
Message msg = new Message("TopicTest", "TagA", "KEY", "Hello RocketMQ".getBytes());
SendResult sendResult = producer.send(msg);
System.out.printf("Send Result: %s%n", sendResult);
producer.shutdown();
消费者接收普通消息:
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("regular_consumer_group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("TopicTest", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf("Received Message: %s%n", new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
定时或延迟消息(Delayed Messages)
定时消息是 Apache RocketMQ 提供的一种高级消息类型,消息被发送至服务端后,在指定时间后才能被消费者消费。通过设置一定的定时时间可以实现分布式场景的延时调度触发效果。
定时时间设置原则
-
Apache RocketMQ 定时消息设置的定时时间是一个预期触发的系统时间戳,延时时间也需要转换成当前系统时间后的某一个时间戳,而不是一段延时时长。
-
定时时间的格式为毫秒级的Unix时间戳,您需要将要设置的时刻转换成时间戳形式。具体方式,请参见Unix时间戳转换工具。
-
定时时间必须设置在定时时长范围内,超过范围则定时不生效,服务端会立即投递消息。
-
定时时长最大值默认为24小时,不支持自定义修改,更多信息,请参见参数限制。
-
定时时间必须设置为当前时间之后,若设置到当前时间之前,则定时不生效,服务端会立即投递消息。
示例如下:
-
定时消息:例如,当前系统时间为2022-06-09 17:30:00,您希望消息在下午19:20:00定时投递,则定时时间为2022-06-09 19:20:00,转换成时间戳格式为1654773600000。
-
延时消息:例如,当前系统时间为2022-06-09 17:30:00,您希望延时1个小时后投递消息,则您需要根据当前时间和延时时长换算成定时时刻,即消息投递时间为2022-06-09 18:30:00,转换为时间戳格式为1654770600000。
定时消息生命周期
-
初始化:消息被生产者构建并完成初始化,待发送到服务端的状态。
-
定时中:消息被发送到服务端,和普通消息不同的是,服务端不会直接构建消息索引,而是会将定时消息单独存储在定时存储系统中,等待定时时刻到达。
-
待消费:定时时刻到达后,服务端将消息重新写入普通存储引擎,对下游消费者可见,等待消费者消费的状态。
-
消费中:消息被消费者获取,并按照消费者本地的业务逻辑进行处理的过程。 此时服务端会等待消费者完成消费并提交消费结果,如果一定时间后没有收到消费者的响应,Apache RocketMQ会对消息进行重试处理。具体信息,请参见消费重试。
-
消费提交:消费者完成消费处理,并向服务端提交消费结果,服务端标记当前消息已经被处理(包括消费成功和失败)。 Apache RocketMQ 默认支持保留所有消息,此时消息数据并不会立即被删除,只是逻辑标记已消费。消息在保存时间到期或存储空间不足被删除前,消费者仍然可以回溯消息重新消费。
-
消息删除:Apache RocketMQ按照消息保存机制滚动清理最早的消息数据,将消息从物理文件中删除。更多信息,请参见消息存储和清理机制。
使用限制
消息类型一致性
定时消息仅支持在 MessageType为Delay 的主题内使用,即定时消息只能发送至类型为定时消息的主题中,发送的消息的类型必须和主题的类型一致。
定时精度约束
Apache RocketMQ 定时消息的定时时长参数精确到毫秒级,但是默认精度为1000ms,即定时消息为秒级精度。
Apache RocketMQ 定时消息的状态支持持久化存储,系统由于故障重启后,仍支持按照原来设置的定时时间触发消息投递。若存储系统异常重启,可能会导致定时消息投递出现一定延迟。
使用场景
- 分布式定时调度:电商系统中的订单如果在一定时间内未支付,需要自动取消。
- 定时提醒:设置定时提醒功能,按照预定时间发送提醒消息。
原理机制
RocketMQ 的延迟消息机制基于延迟级别(Delay Levels),每个级别对应固定的延迟时间。其原理如下:
- 延迟级别配置:RocketMQ 在 Broker 中预定义了多个延迟级别(从 1 秒到 24 小时),每个级别对应一个固定的延迟时间。
- 消息发送:生产者发送消息时设置延迟级别(如设置为 3 代表 10 秒延迟)。消息首先存储在延迟队列中。
- 延迟队列:消息在延迟队列中等待到达指定的延迟时间。一旦延迟时间到达,消息会被转移到实际的消费队列中进行消费。
优缺点
- 优点:
- 简洁实现:通过设置延迟级别实现延迟功能,无需额外的编程。
- 性能高效:基于队列的延迟处理,性能开销小。
- 缺点:
- 延迟级别固定:只能使用预定义的延迟级别,灵活性有限。
- 精度问题:延迟时间的精度受到延迟级别的限制,可能会有一定误差。
代码示例
发送延迟消息:
DefaultMQProducer producer = new DefaultMQProducer("delay_producer_group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
Message msg = new Message("TopicTest", "TagA", "KEY", "Hello RocketMQ".getBytes());
msg.setDelayTimeLevel(3); // 设置延迟级别,3 代表10秒延迟
SendResult sendResult = producer.send(msg);
System.out.printf("Send Result: %s%n", sendResult);
producer.shutdown();
顺序消息(Message Ordering)
顺序消息是 Apache RocketMQ 提供的一种高级消息类型,支持消费者按照发送消息的先后顺序获取消息,从而实现业务场景中的顺序处理。 相比其他类型消息,顺序消息在发送、存储和投递的处理过程中,更多强调多条消息间的先后顺序关系。
Apache RocketMQ 顺序消息的顺序关系通过消息组(MessageGroup)判定和识别,发送顺序消息时需要为每条消息设置归属的消息组,相同消息组的多条消息之间遵循先进先出的顺序关系,不同消息组、无消息组的消息之间不涉及顺序性。
基于消息组的顺序判定逻辑,支持按照业务逻辑做细粒度拆分,可以在满足业务局部顺序的前提下提高系统的并行度和吞吐能力。
顺序消息生命周期
-
初始化:消息被生产者构建并完成初始化,待发送到服务端的状态。
-
待消费:消息被发送到服务端,对消费者可见,等待消费者消费的状态。
-
消费中:消息被消费者获取,并按照消费者本地的业务逻辑进行处理的过程。 此时服务端会等待消费者完成消费并提交消费结果,如果一定时间后没有收到消费者的响应,Apache RocketMQ会对消息进行重试处理。具体信息,请参见消费重试。
-
消费提交:消费者完成消费处理,并向服务端提交消费结果,服务端标记当前消息已经被处理(包括消费成功和失败)。 Apache RocketMQ 默认支持保留所有消息,此时消息数据并不会立即被删除,只是逻辑标记已消费。消息在保存时间到期或存储空间不足被删除前,消费者仍然可以回溯消息重新消费。
-
消息删除:Apache RocketMQ按照消息保存机制滚动清理最早的消息数据,将消息从物理文件中删除。更多信息,请参见消息存储和清理机制。
备注
-
消息消费失败或消费超时,会触发服务端重试逻辑,重试消息属于新的消息,原消息的生命周期已结束。
-
顺序消息消费失败进行消费重试时,为保障消息的顺序性,后续消息不可被消费,必须等待前面的消息消费完成后才能被处理。
使用限制
顺序消息仅支持使用MessageType为FIFO的主题,即顺序消息只能发送至类型为顺序消息的主题中,发送的消息的类型必须和主题的类型一致。
使用场景
- 订单处理:需要确保订单的创建、支付、发货等操作按顺序处理。
- 金融交易:确保转账和扣款等操作按时间顺序进行。
原理机制
RocketMQ 通过两种主要机制支持顺序消息:
-
全局顺序:
- 机制:所有消息发送到同一个队列,消费者从这个队列中顺序拉取消息。适用于顺序要求非常严格的场景。
- 实现:生产者将消息发送到一个单独的队列中,消费者顺序拉取,确保顺序性。
-
分区顺序:
- 机制:通过分区键(如订单 ID)将消息路由到特定的队列。每个队列内的消息按顺序消费,但不同队列之间的顺序不保证。
- 实现:生产者基于某个分区键将消息发送到特定队列,消费者从这些队列中按顺序消费。
优缺点
- 优点:
- 顺序保证:适用于需要严格顺序处理的场景。
- 扩展性:分区顺序机制在保证顺序的同时支持高吞吐量。
- 缺点:
- 全局顺序性能瓶颈:全局顺序适用于小规模应用,高负载下可能成为性能瓶颈。
- 分区顺序局限:只能保证同一队列的顺序,跨队列的顺序无法保证。
代码示例
生产者发送顺序消息:
DefaultMQProducer producer = new DefaultMQProducer("order_producer_group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
String[] tags = new String[]{"TagA", "TagB", "TagC"};
for (int i = 0; i < 10; i++) {
Message msg = new Message("TopicTest", tags[i % tags.length], "KEY" + i, ("Order " + i).getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
int queueNumber = arg.hashCode() % mqs.size();
return mqs.get(queueNumber);
}
}, i); // i 作为分区键
System.out.printf("Send Result: %s%n", sendResult);
}
producer.shutdown();
消费者消费顺序消息:
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order_consumer_group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("TopicTest", "*");
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf("Received Message: %s%n", new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
事务消息(Transactional Messages)
事务消息是 Apache RocketMQ 提供的一种高级消息类型,支持在分布式场景下保障消息生产和本地事务的最终一致性。
事务消息生命周期
-
初始化:半事务消息被生产者构建并完成初始化,待发送到服务端的状态。
-
事务待提交:半事务消息被发送到服务端,和普通消息不同,并不会直接被服务端持久化,而是会被单独存储到事务存储系统中,等待第二阶段本地事务返回执行结果后再提交。此时消息对下游消费者不可见。
-
消息回滚:第二阶段如果事务执行结果明确为回滚,服务端会将半事务消息回滚,该事务消息流程终止。
-
提交待消费:第二阶段如果事务执行结果明确为提交,服务端会将半事务消息重新存储到普通存储系统中,此时消息对下游消费者可见,等待被消费者获取并消费。
-
消费中:消息被消费者获取,并按照消费者本地的业务逻辑进行处理的过程。 此时服务端会等待消费者完成消费并提交消费结果,如果一定时间后没有收到消费者的响应,Apache RocketMQ会对消息进行重试处理。具体信息,请参见消费重试。
-
消费提交:消费者完成消费处理,并向服务端提交消费结果,服务端标记当前消息已经被处理(包括消费成功和失败)。 Apache RocketMQ默认支持保留所有消息,此时消息数据并不会立即被删除,只是逻辑标记已消费。消息在保存时间到期或存储空间不足被删除前,消费者仍然可以回溯消息重新消费。
-
消息删除:Apache RocketMQ按照消息保存机制滚动清理最早的消息数据,将消息从物理文件中删除。更多信息,请参见消息存储和清理机制。
使用限制
消息类型一致性
事务消息仅支持在 MessageType 为 Transaction 的主题内使用,即事务消息只能发送至类型为事务消息的主题中,发送的消息的类型必须和主题的类型一致。
消费事务性
Apache RocketMQ 事务消息保证本地主分支事务和下游消息发送事务的一致性,但不保证消息消费结果和上游事务的一致性。因此需要下游业务分支自行保证消息正确处理,建议消费端做好消费重试,如果有短暂失败可以利用重试机制保证最终处理成功。
中间状态可见性
Apache RocketMQ 事务消息为最终一致性,即在消息提交到下游消费端处理完成之前,下游分支和上游事务之间的状态会不一致。因此,事务消息仅适合接受异步执行的事务场景。
事务超时机制
Apache RocketMQ 事务消息的生命周期存在超时机制,即半事务消息被生产者发送服务端后,如果在指定时间内服务端无法确认提交或者回滚状态,则消息默认会被回滚。事务超时时间,请参见参数限制。
使用场景
- 跨系统事务:确保分布式系统中的事务一致性,如银行转账。
- 订单创建与库存管理:确保订单创建和库存扣减的一致性。
原理机制
RocketMQ 的事务消息机制包括:
-
预提交消息:
- 机制:生产者将消息发送到 Broker,但消息对消费者不可见。这是事务的第一阶段。
- 实现:消息存储在特殊的预提交状态,直到本地事务结果被确认。
-
本地事务执行:
- 机制:生产者在本地系统中执行事务操作(如数据库操作),并根据结果决定是否提交或回滚消息。
-
事务提交或回滚:
- 机制:生产者通知 Broker 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
- 实现:生产者通过调用接口确认事务状态。
-
事务状态回查:
- 机制:Broker 在超时后回查生产者的事务状态以决定消息是否提交或回滚。这是为了处理网络异常等问题。
优缺点
- 优点:
- 事务支持:支持分布式事务,保证消息传递的一致性。
- 高效性:通过两阶段提交机制提高了事务处理效率。
- 缺点:
- 实现复杂:需要处理本地事务和回查机制,增加了系统复杂度。
- 回查延迟:回查机制可能导致事务状态确认的延迟,影响实时性。
代码示例
构建事务消息:
TransactionMQProducer producer = new TransactionMQProducer("trans_producer_group");
producer.setNamesrvAddr("localhost:9876");
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
// 执行本地事务逻辑
// 如果本地事务成功
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
// 如果本地事务失败
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// 回查事务状态
return LocalTransactionState.COMMIT_MESSAGE;
}
});
producer.start();
Message msg = new Message("TopicTest", "TagA", "KEY", "Hello RocketMQ".getBytes());
producer.sendMessageInTransaction(msg, null);
总结
通过这些实践,可以深入理解 RocketMQ 的高级特性,并在实际项目中应用这些功能来处理复杂的消息传递需求。