组成:
nameServer 提供注册中心的服务,负责broker的管理,以及topic路由信息的管理。
brokerServer 则主要负责消息的存储、投递和查询及高可用。
Producer 连接nameServer获取到broker信息后,发送信息到对应的broker。
Consumer 同样先连接 nameServer,查询topic路由信息,然后连接broker消费消息。
延迟消息:
定时是设定未来具体时间要干某事,延时则是推迟多久干某事
使用场景:
典型场景一:分布式定时任务(延迟发送邮件)
典型场景二:超时处理(订单超时未支付的场景)
典型场景三:限时优惠活动(商品促销,活动开始,发送一个两小时后触发的定时消息,活动结束时恢复原价。)
典型场景四:工作流程控制(某个步骤完成,下个步骤自动完成)
RocketMQ 中,一共内置了 18 个等级的延时时间,也就是 Producer 无法随便选择延时时间,而是只能在这 18 个时间里面选择。发送延时消息只需要 setDelayTimeLevel 就可以。
原理:
1、Producer 发送延时消息后,会立马给到 Broker
2、Broker 将会将该消息发送到一个新的专门存放延时消息的 Topic,这个 Topic 的名字就叫 SCHEDULE_TOPIC_XXXX 。这个 Topic 中一共有 18 个队列用来对应 18 个等级的延时 level。按照消息延时 level (从预设的 18 个里面选)进入到不同的队列中。
3、这个和原先那套存储类似,Broker 会将消息存储到 commitlog 下,也会保存一份数据到 consumerQueue(这里有些懵的可以看之前的存储篇)。
4、在 Broker 内部有核心线程池,里面有 18 个关于延时消息的线程,专门用来监察有没得延时消息。每个线程消费对应队列的消息,每 100 ms 消费一次,不断循环,消费逻辑就是得到队列中的 consumerQueue 和 offset,就可以获得延时消息,拿到延时消息的延迟时间和当前时间对比,判断是否到期,如果到期,则会将消息丢回原 Topic 和队列。
5、你也可以理解是 RocketMQ 的一种消息过滤,在消息过滤篇也提过一嘴,但内部实现原理就是基于 Broker 内部线程池定时调度消费延时队列中消息。
所以回到上面的问题,为什么要不允许用户自定义延时时间呢?原因还是 RocketMQ 内置了不同 level 队列对应的线程,这样能减少时间轮的复杂度,避免系统滥用,减少了动态计算和管理不同延时时间的复杂性,性能方面也得到了极致的优化。
实战:
1、创建生产者
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
public class DelayMessageProducer {
public static void main(String[] args) throws Exception {
// 创建一个生产者,并指定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("delay_producer_group");
// 指定NameServer地址
producer.setNamesrvAddr("localhost:9876");
// 启动生产者
producer.start();
for (int i = 0; i < 10; i++) {
// 创建消息实例,指定主题、标签和消息体
Message msg = new Message("DelayTopic", "DelayTag",
("Hello RocketMQ " + i).getBytes());
// 设置延时等级3,对应10s
msg.setDelayTimeLevel(3);
// 发送消息
SendResult sendResult = producer.send(msg);
System.out.printf("发送结果:%s%n", sendResult);
}
// 关闭生产者
producer.shutdown();
}
}
2、创建消费者
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class DelayMessageConsumer {
public static void main(String[] args) throws Exception {
// 创建一个消费者,并指定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("delay_consumer_group");
// 指定NameServer地址
consumer.setNamesrvAddr("localhost:9876");
// 订阅主题和标签
consumer.subscribe("DelayTopic", "*");
// 注册回调接口来处理从broker拉取的消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf("接收到消息:%s %n", new String(msg.getBody()));
}
// 标记该消息已经被成功消费
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消费者
consumer.start();
System.out.println("消费者已启动");
}
}