【转载】RocketMQ是怎么跑起来的?

原文链接:https://juejin.cn/post/7091096519947862023
作者:pinnuli

RocketMQ是怎么跑起来的?

一说到RocketMQ,很多人人都知道其有四大组件,NameServer、Broker、Producer、Consumer,也大概都知道这四个组件分别有什么作用,负责什么功能,但是不知道这四个组件是如何配合的。

今天我们就来聊聊,这四个组件如何配合,通过哪些核心的API或者组件进行通讯配合,让RocketMQ跑起来,完成一条消息的产生和消费。

这里只会讲几个组件配合的核心流程和核心API,形成一个整体的框架,以便于在以后遇到问题的时候,能大致定位到问题所在,知道应该去看哪些内容。至于一些源码的深入解读,例如Broker消息是如何存储的,Producer、Consumer的负载均衡算法有哪些,延迟消息、顺序消息等是如何实现的,不是本文重点。

NameServer

NameServer的作用

  1. 维护Broker的服务地址,即各个Topic下的各个queue所在的地址;
  2. 给Producer、Consumer提供各个Topic的Broker列表。

NameServer的核心启动流程

我们先看一下NameServer在启动的过程中,做了哪些核心的动作

NameServer启动流程时序图.png

  1. NamesrvStartup是NameServer服务的启动类,其main方法中,调用了方法createNamesrvController创建了一个NamesrvController

  2. createNamesrvController方法中执行了以下操作

  • 首先创建了一个NamesrvConfig(一些NameServer自身运行的参数,例如配置文件路径等),
  • 然后创建了一个NettyServerConfig(一些Netty的参数),并绑定了9876端口,这个端口就是对外提供网络服务的入口
  • 接着处理了一些初始化动作,例如日志打印的配置,环境变量加载等;
  • 然后创建一个NamesrvController对象,在对象的构造方法中注入了上面创建的NamesrvConfigNettyServerConfig等对象,以及创建了一个RouteInfoManager,这个对象用于管理Broker的路由信息
  1. 创建完NamesrvController,进行初始化,其中两个核心的动作:
  • 创建一个NettyRemotingServer,用于处理网络请求,例如Broker的注册和心跳,Producer获取topic的路由数据等,也就是说NameServer基本上是通过这个组件跟外部进行通讯;
  • 开启一些定时任务,例如定时清除不活跃的Broker,定时打印一些配置等。

可以参照源码看一下,还是比较容易看懂的,毕竟是国人写的代码

NameServer的顶层结构

知道了NameServer启动过程中做了哪些事情,可以大概整理出,NameServer在RocketMQ运行过程中,顶层的一些结构。

NameServer顶层结构.png

Broker

Broker的作用

整个RocketMQ最核心的部分,消息的存储、转发都由他完成:
1、Producer发送消息给Broker,由Broker负责存储;
2、Consumer从Broker拉消息进行消费(推送模式实际上最终也是由拉消息实现)。

Broker的核心启动流程

我们再看一下Broker在启动的过程中,做了哪些核心的动作,
代码核心流程和NameServer的启动流程非常相似,主要是创建和初始化的组件、定时任务、线程池。

Broker启动流程时序图.png

  1. BrokerStartup是Broker服务的启动类,其main方法中,调用了方法createBrokerController创建了一个BrokerController
  2. createBrokerController方法中执行了以下操作
  • 创建了一个BrokerConfig(一些Broker自身运行的参数,例如NameServer地址等等),
  • 创建了一个NettyServerConfig(一些Netty的参数),作为netty服务端的参数,并绑定了10911端口,这个端口作为接收Producer、Consumer网络请求的入口
  • 创建了一个NettyClientConfig(一些Netty的参数),作为netty客户端的参数,;
  • 创建了一个MessageStoreConfig主要是用来消息存储的一些配置,例如Commitlog文件的大小等;
  • 处理了一些初始化动作,例如日志打印的配置,环境变量加载等;
  • 创建一个BrokerController对象,在对象的构造方法中注入了上面创建的BrokerConfigNettyServerConfigNettyClientConfigMessageStoreConfig等对象,以及创建了一个BrokerOuterAPI,这个对象用于发送客户端请求,例如向NameServer注册、向Producer请求事务消息状态等
  1. 创建完BrokerController,进行初始化,其中四类核心的动作:
  • 创建一个DefaultMessageStore,用于管理消息在磁盘上的存储
  • 创建一个NettyRemotingServer,用于处理网络请求,例如Producer发送过来的消息、Consumer拉去消息等;也就是说Broker用这个组件个Producer、Consumer通讯
  • 初始化一些线程池,例如处理consumer拉消息的线程池、向NameServer发送心跳的线程池等;
  • 开启一些定时任务,用了例如定时清除不活跃的Broker,定时打印一些配置等。

Broker的顶层结构

上面介绍了Broker在启动过程中,NettyRemotingServerBrokerOuterAPIMessageStoreConfig核心对象,至此我们也可以梳理出Broker通过哪些核心对象,与RocketMQ的其他组件配合,完成一条消息的发送、存储、消费。

Broker启动流程.png

Producer

Producer的启动和使用的核心流程

Produce顶层涉及到的组件不多,我们看下RocketMQ源码中提供的最基本使用例子,根据代码的调用流程,梳理出其启动和发送消息的核心方法,主要在DefaultMQProducerImpl

public static void main(String[] args) throws MQClientException, InterruptedException {

    DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");

    producer.start();

    for (int i = 0; i < 10000000; i++)
        try {
            {
                Message msg = new Message("TopicTest",
                    "TagA",
                    "OrderID188",
                    "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    producer.shutdown();
}

Producer使用核心流程时序图.png
创建Producer,并发送一条消息,核心流程如下:

  1. 创建一个默认的生产者实现实例DefaultMQProducerImpl
  2. 启动该实例,过程中创建了一个MQ客户端工厂实例MQClientInstance
  3. 构造消息并发送,发送的方法最终调到的是DefaultMQProducerImpl.sendDefaultImpl()这个方法的实现过程才是核心,主要有以下步骤:
  • topicPublishInfoTable()根据消息的topic的路由信息,会先从本地缓存找,找不到再到NameServer上申请
  • selectOneMessageQueue()根据topic选择一条队列,默认是负载均衡策略将消息平均分配到每个队列上;
  • sendKernelImpl()根据消息、Broker信息、获取到的队列等,将消息发送出去,这个方法主要通过MQ客户端实例,即MQClientInstance发送消息,首先是用BrokerName兑换Broker地址,如果拿不到会去NameServer获取,然后将消息发送出去,至此消息发送完毕。也就是Producer用MQClientInstance的实现类与其他组件进行通讯,包括从NameServer拉取Broker列表,向Producer发送消息等

Producer的顶层结构

我们在将消息的发送过程,加入到上面整理出来的四大组件配合过程中

Broker-NameServer-Producer配合.png

Consumer

上面我们说完了NameServer、Broker、Producer,大图中就差最后一个角色Consumer就大功告成了。
Consumer我们也用和Producer差不多的思路去梳理。

Consumer的启动和使用的核心流程

我们同样看下RocketMQ源码中提供的最基本使用例子,其核心逻辑主要在DefaultMQPushConsumerImpl

public static void main(String[] args) throws InterruptedException, MQClientException {

    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");

    consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

    consumer.subscribe("TopicTest", "*");

    consumer.registerMessageListener(new MessageListenerConcurrently() {

        @Override
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
            ConsumeConcurrentlyContext context) {
            System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });

    consumer.start();

    System.out.printf("Consumer Started.%n");
}

Consumer启动消费流程时序图.png

创建Consumer,订阅某个topic,并注册监听器,核心流程如下:

  1. 创建一个默认的消费者实现实例DefaultMQPushConsumerImpl,这里会同时创建两个核心组件:
  • PullMessageService,负责从Broker拉去消息;
  • RebalanceService,负责控制队列的负载均衡;
  1. 设置消费起始偏移量
  2. 订阅topic
  3. 注册监听器,即消费逻辑
  4. 启动消费者实例,这里是核心,会创建两个实例
  • MQClientInstance,MQ客户端实例,用于从Broker拉消息、从NameServer拉取Broker列表跟Producer一样,Consumer也是通过这个MQ客户端实例与外部进行通讯
  • ConsumeMessageService消息消费组件,会根据不同的消费模式创建不同的实例,例如顺序消费和并发消费,从Broker收到消息后,也是由这个组件完成消费;创建完该组件也会马上启动。
    之后会启动MQClientInstance,在这个过程中会启动PullMessageServiceRebalanceService

Consumer的顶层结构

我们同样把Consumer的消息拉取、消费的核心流程和组件,梳理出其顶层架构

Broker-NameServer-Producer-Consumer配合.png

NameServer、Broker、Producer、Consumer的配合过程

NameServer

通过组件NettyRemoteServer,绑定9876端口(也可手动设置其他端口),作为服务端,与其他三个组件通信,完成Broker的注册,向Producer、Consumer提供Broker信息;

Broker

通过组件NettyRemoteServer,绑定10911端口,作为服务端,接收Producer发消息、Consumer拉消息等网络请求;
通过组件BrokerOuterAPI,作为客户端,向NameServer注册和发送心跳等;

Producer

通过MQClientInstance,作为客户端,向NameServer拉去Broker信息、向Broker发送消息;

Consumer

通过MQClientInstance,作为客户端,向NameServer拉去Broker信息、向Broker拉取消息;

至此,我们全部梳理完了四个组件的基本启动流程和核心组件,我们整合到一起,就形成了RocketMQ的整个运行流程:
Broker-NameServer-Producer-Consumer配合.png

RocketMQ中,时间到限(TTL, Time To Live) 是指消息在队列中等待消费的时间限制。如果你想要设置消息的TTL,你需要在生产者发送消息时指定这个属性。RocketMQ提供了`MessageExt`接口,你可以通过设置`MessageExt.setBornTimestamp()`方法来自定义消息的创建时间(默认为当前时间),然后使用`MessageExt.setExpireTime()`方法设置消息的有效期。 以下是一个简单的示例,展示了如何在Java生产者中设置消息的TTL: ```java import com.alibaba.rocketmq.client.producer.MessageProducer; import com.alibaba.rocketmq.client.producer.SendResult; import com.alibaba.rocketmq.client.producer.SendStatus; import com.alibaba.rocketmq.client.producer.MessageExt; // 创建消息并设置TTL String topic = "your_topic"; ProducerConfig producerConfig = new ProducerConfig("your_producer_group"); MessageProducer producer = new MessageProducer(producerConfig); SendResult sendResult = null; try { // 自定义消息创建时间和有效期 long bornTimestamp = System.currentTimeMillis(); // 或者设置自定义时间 long expireTimestamp = bornTimestamp + (60 * 1000); // 设置60秒后过期 // 创建MessageExt实例 MessageExt msg = new MessageExt(topic, // topic "tag", // tag "", // key "your_message_body".getBytes(), // message body bornTimestamp, expireTimestamp); // 发送消息 sendResult = producer.send(msg); if (sendResult.getSendStatus() == SendStatus.SEND_OK) { System.out.println("Message sent with TTL successfully."); } else { System.out.println("Failed to send message with TTL: " + sendResult.getErrorMessage()); } } catch (Exception e) { e.printStackTrace(); } finally { if (producer != null) { producer.shutdown(); } } ``` 请注意,实际使用时,你需要替换上述代码中的"your_topic", "your_producer_group", "your_message_body"等为你自己的配置信息。同时,TTL是在消费者拉取消息时才会检查的消息过期策略,如果消费者长时间没有拉取,消息也可能因为其他原因(如内存限制)被自动删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值