RocketMQ篇三(源码解读、NameServer启动、Broker启动、Broker注册、Producer、消息存储、Consumer消费者、延迟消息)

1. NameServer启动

在RocketMQ中,实际进行消息存储、推送等核心功能的是Broker,NameServer的核心作用其实只有两个:

  • 维护Broker的服务地址并进行及时的更新
  • 给Producer和Consumer提供服务获取Broker列表

NameServer启动入口为NamesrvStartup类的main方法,整体流程如下:
NameServer启动流程
NameServer的核心就是一个NamesrvController对象。这个Controller对象就跟Java Web开发中的Controller功能类似,都是响应客户端请求的。在创建NamesrvController对象时,有两个关键的配置

  • NamesrvConfig:这个是NameServer自己运行需要的配置信息。
  • NettyServerConfig包含Netty服务端的配置参数,默认占用了9876端口。可以在配置文件中覆盖。

然后在启动服务时,启动几个重要组件:

  • RemotingServer:用来响应请求。
  • 还有一个定时任务会定时扫描不活动的Broker。这个Broker管理是通过routeInfoManager这个功能组件。

在关闭服务时,关闭了四个东西:

  • RemotingServer
  • remotingExecutor Netty服务线程池
  • scheduledExecutorService 定时任务
  • fileWatchService 这个是用来跟踪TLS配置的。这是跟权限相关的,暂不关注。

综上,可以总结出NameServer的组件其实并不是很多,整个NameServer的结构是这样:
NameServer结构

2. Broker启动

Broker是整个RocketMQ的业务核心,所有消息存储、转发这些最为重要的业务都是在Broker中进行处理的。而Broker的内部架构,有点类似于Java Web开发的MVC架构。有Controller负责响应请求,各种Service组件负责具体业务,然后还有负责消息存盘的功能模块则类似于DAO。

Broker启动的入口在BrokerStartup这个类,启动过程关键点:重点也是围绕一个BrokerController对象,先创建,然后再启动。

首先:在BrokerStartup.createBrokerController方法中可以看到Broker的几个核心配置:

  • BrokerConfig
  • NettyServerConfig:Netty服务端占用了10911端口。同样也可以在配置文件中覆盖。
  • NettyClientConfig
  • MessageStoreConfig

然后:在BrokerController.start方法可以看到穷了一大堆Broker的核心服务,重要的如下:

this.messageStore.start();启动核心的消息存储组件
this.remotingServer.start();
this.fastRemotingServer.start(); 启动两个Netty服务
this.brokerOuterAPI.start();启动客户端,往外发请求
BrokerController.this.registerBrokerAll: 向NameServer注册心跳。
this.brokerStatsManager.start();
this.brokerFastFailure.start();这也是一些负责具体业务的功能组件

综上,Broker的整体结构如下:
Broker结构

3. Broker注册

Broker会在启动时向NameServer注册自己的服务信息,并且会定时的往NameServer发送心跳信息。而NameServer会维护Broker的路由列表,并对路由列表进行实时更新。

BrokerController.this.registerBrokerAll方法会发起向NameServer注册心跳。启动时会立即注册,同时也会启动一个线程池,以10秒延迟,默认30秒的间隔,持续向NameServer发送心跳。流程如下:
Broker注册心跳
然后,在NameServer中也会启动一个定时任务,扫描不活动的Broker。具体观察NamesrvController.initialize方法。

4. Producer

Producer有两种

  • 普通发送者:DefaultMQProducer,这个只需要构建一个Netty客户端,往Broker发送消息就行了。注意:异步回调知识在Producer接收到Broker的响应后自行调整流程,不需要提供Netty服务。
  • 事务消息发送者:TransactionMQProducer,这个需要构建一个Netty客户端,往Broker发送消息,同时也要构建Netty服务端,供Broker回查本地事务状态。

整个Producer的流程,大致分为两个步骤:

  • start方法,进行一大堆的准备工作
  • 各种各样的send方法,进行消息发送。

Producer需要拉取Broker列表,然后跟Broker建立连接等等很多核心的流程,其实都会在发送消息时建立的。因为在启动时,还不知道要拉取哪个Topic的Broker列表,Send方法首先会从本地缓存中获取Topic的路由信息,如果本地缓存没有,就从NameServer中去申请。核心在org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#tryToFin dTopicPublishInfo方法。

关于Producer的负载均衡,默认会把消息平均的发送到所有MessageQueue里,具体的负载均衡方式是:在获取路由信息后,会选出一个MessageQueue去发送消息。这个MessageQueue的方法就是一个索引自增然后取模的方式。然后在发送Netty请求时,实际上指定的是MessageQueue,而不是Topic。Topic知识用来找MessageQueue。然后根据MessageQueue再找所在的Broker,往Broker发送请求。

5. 消息存储

Producer把消息发送到Broker之后,Broker接收到消息之后会把消息进行存储。最终的存储文件有以下几种:

  • commitLog:消息存储目录
  • config:运行期间一些配置信息
  • consumerqueue:消息消费队列存储目录
  • index:消息索引文件存储目录
  • abort:如果存在该文件,说明Broker非正常关闭
  • checkpoint:文件检查点,存储CommitLog文件最后一次刷盘时间戳、consumerqueue最后一次刷盘时间戳、index索引文件最后一次刷盘时间戳。

消息存储的入口在DefaultMessageStore.putMessage

  • commitLog写入:CommitLog的soAppend方法就是Broker写入消息的实际入口。这个方法最终会把消息追加到MappedFile映射的一块内存里,并没有直接写入磁盘,写入消息的过程是串行的,一次只会允许一个线程写入。
  • 分发ConsumerQueue和IndexFile:当CommitLog写入一套消息后,在DefaultMessageStore的start方法中,会启动一个后台线程reputMessageService每隔1毫秒就会去拉取CommitLog中最小更新的一批消息,然后分别转发到ConsumerQueue和IndexFile里去,这就是最底层的实现逻辑。 并且,如果服务异常宕机,会造成CommitLog、ConsumerQueue、IndexFile文件不一致,有消息写入CommitLog后,没有分发到索引文件,这样消息就丢失了。DefaultMappedStore的load方法提供了恢复索引文件的方法,入口在load方法。
  • 文件同步刷盘与异步刷盘:入口:CommitLog.putMessage -> CommitLog.handleDiskFlush,其中主要涉及到是否开启了对外内存。TransientStorePoolEnable。如果开启了堆外内存,会在启动时申请一个跟CommitLog文件大小一致的堆外内存,这部分内存就可以确保不会被交换到虚拟内存中。
  • 过期文件删除:入口:DefaultMessageStore.addScheduleTask -> DefaultMessageStore.this.cleanFilesPeriodically(),默认情况下,Broker会启动后台线程,每60秒,检查CommitLog、ConsumerQueue文件,然后对超过72小时的数据进行删除,即RocketMQ只会保存3天内的数据(可以通过fileReservedTime来配置),删除时并不会检查消息是否被消费了。

整个文件存储的核心入口在DefaultMessageStore的start方法中。
RocketMQ消息存储
总结:RocketMQ的存储文件包括消息文件(CommitLog)、消息消费队列文件(ConsumerQueue)、Hash索引文件(IndexFile)、监测点文件(checkPoint)、abort(关闭异常文件)。单个消息存储文件、消息消费队列文件、Hash索引文件长度固定以便使用内存映射机制来进行文件的读写操作。RocketMQ基于内存映射文件机制提供了同步刷盘和异步刷盘两种机制,异步刷盘是指在消息存储时先追加到内存映射文件,然后启动专门的刷盘线程定时的将内存中的文件数据刷写到磁盘。

CommitLog:消息存储文件,RocketMQ为了保证消息发送的高吞吐量,采用单一文件存储所有主题消息,保证消息存储时完全的顺序写,但这样给文件读取带来了不便,为此RocketMQ问了方便消息消费构建了消息消费队列文件,基于主题与队列进行组织,同时RocketMQ为消息实现了Hash索引,可以为消息设置索引键,能够快速从CommitLog文件中检索消息。

当消息到达CommitLog后,会通过ReputMessageService线程接近实时地将消息转发给消息队列文件与索引文件。为了安全起见,RocketMQ引入abort文件,记录Broker的停机是正常关闭还是不异常关闭,在重启Broker时为了保证CommitLog文件,消息消费队列文件与Hash索引文件的正确性,分别采用不同策略来恢复文件。

RocketMQ不会永久存储消息文件、消息消费队列文件,而是启动文件过期机制并在磁盘空间不足或者默认凌晨四点删除过期文件,文件保存72小时并且在删除文件时并不会判断该消息文件上的消息是否被消费。

6. Consumer消费者

消费者也是两种:推模式消费者和拉模式消费者。消费者的使用过程也跟生产者差不多,都是先start()然后再开始消费。DefaultMQPushConsumer.start方法。

客户端启动的核心是mQClientFactory 主要是启动了一大堆的服务。例如pullMessageService主要处理拉取消息服务,rebalanceService主要处理客户端的负载均衡。

拉模式:PullMessageService,PullRequest里有messageQueue和processQueue,其中messageQueue负责拉取消息,拉取到后,将消息存入processQueue,进行处理。 存入后就可以清空messageQueue,继续拉取了。
RocketMQ pull模式
客户端负载均衡策略:在消费者示例的start方法中,启动RebalanceService,这个是客户端进行负载均衡策略的启动服务。他只负责根据负载均衡策略获取当前客户端分配到的MessageQueue示例。五种负载策略,可以由Consumer的allocateMessageQueueStrategy属性来选择。最常用的是AllocateMessageQueueAveragely平均分配和AllocateMessageQueueAveragelyByCircle平均轮询分配。平均分配是把MessageQueue按组内的消费者个数平均分配。而平均轮询分配就是把MessageQueue按组内的消费者一个一个轮询分配。

例如,六个队列q1,q2,q3,q4,q5,q6,分配给三个消费者c1,c2,c3
平均分配的结果就是: c1:{q1,q2},c2:{q3,q4},c3{q5,q6}
平均轮询分配的结果就是: c1:{q1,q4},c2:{q2,q5},c3:{q3,q6}

并发消费与顺序消费的过程:消费的过程依然是在DefaultMQPushConsumerImpl的consumeMessageService中。他有两个子类ConsumeMessageConcurrentlyService和ConsumeMessageOrderlyService。其中最主要的差别是ConsumeMessageOrderlyService会在消费前把队列锁起来,优先保证拉取同一个队列里的消息。消费过程的入口在DefaultMQPushConsumerImpl的pullMessage中定义的PullCallback中。

7. 延迟消息

延迟消息的核心使用方法就是在Message中设定一个MessageDelayLevel参数,对应18个延迟级别。然后Broker中会创建一个默认的Schedule_Topic主题,这个主题下有18个队列,对应18个延迟级别。消息发过来之后,会先把消息存入Schedule_Topic主题中对应的队列。然后等延迟时间到了,再转发到目标队列,推送给消费者进行消费。实现方式如下:
RocketMQ延迟消息实现
延迟消息的处理入口在scheduleMessageService这个组件中。 他会在broker启动时也一起加载。

消息写入:代码见CommitLog.putMessage方法。在CommitLog写入消息时,会判断消息的延迟级别,然后修改Message的Topic和Queue,达到转储Message的目的。

消息转储到目标Topic:这个转储的核心服务是scheduleMessageService,他也是Broker启动过程中的一个功能组件、然后ScheduleMessageService会每隔1秒钟执行一个executeOnTimeup任务,将消息从延迟队列中写入正常Topic中。 代码见ScheduleMessageService中的DeliverDelayedMessageTimerTask.executeOnTimeup方法。

这个其中有个需要注意的点就是在ScheduleMessageService的start方法中。有一个很关键的CAS操作:

if (started.compareAndSet(false, true)) {

这个CAS操作保证了同一时间只会有一个DeliverDelayedMessageTimerTask执行。保证了消息安全的同时也限制了消息进行回传的效率。所以,这也是很多互联网公司在使用RocketMQ时,对源码进行定制的一个重点。

消费者源码总结

RocketMQ消息消费方式分别为集群模式、广播模式。

消息队列负载由RebalanceService线程默认每隔20s进行一次消息队列负载,根据当前消费者组内消费者个数与主题队列数量按照某一种负载算法进行队列分配,分配原则为同一个消费者可以分配多个消息消费队列,同一个消息消费队列同一个时间只会分配给一个消费者。

消息拉取由PullMessageService线程根据RebalanceService线程创建的拉取任务进行拉取,默认每次拉取32条消息,提交给消费者消费线程后继续下一次消息拉取。如果消息消费过慢产生消息堆积会触发消息消费拉取流控。

并发消息消费指消费线程池中的线程可以并发对同一个消息队列的消息进行消费,消费成功后,取出消息队列中最小的消息偏移量作为消息消费进度偏移量存储在于消息消费进度存储文件中,集群模式消息消费进度存储在Broker(消息服务器),广播模式消息消费进度存储在消费者端。

RocketMQ不支持任意精度的定时调度消息,只支持自定义的消息延迟级别,例如1s、2s、5s等,可通过在broker配置文件中设置messageDelayLevel。

顺序消息一般使用集群模式,是指对消息消费者内的线程池中的线程对消息消费队列只能串行消费。与并发消息消费最本质的区别是消息消费时必须成功锁定消息消费队列,在Broker端会存储消息消费队列的锁占用情况。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 我可以用Java来写一个RocketMQ延迟消息的例子。首先,您需要配置RocketMQ的环境,然后创建一个Producer实例,并且设置消息的属性,将消息发送到指定的topic。在发送消息时,可以设置消息延迟时间,使用sendDelayMsg()方法,消费者将在指定的时间后收到消息。 ### 回答2: RocketMQ是一个开源的分布式消息队列系统,在实现延迟消息时可以使用RocketMQ提供的特性——延迟消息延迟消息是指在发送消息时可以指定该消息延迟时间,消息会在指定延迟时间之后才会被消费者消费。 要写一个RocketMQ延迟消息,首先需要搭建一个RocketMQ的环境,包括安装RocketMQ服务器和创建Producer和Consumer。然后,按照以下步骤实现延迟消息: 1. 创建Producer:在程序中创建一个RocketMQProducer对象,设置NameServer的地址,并启动Producer。 2. 创建延迟消息:使用Producer创建一个延迟消息,包括指定Topic和Tags,并在消息中设置延迟时间,单位为毫秒。 3. 发送消息:使用Producer发送延迟消息。 4. 创建Consumer:在程序中创建一个RocketMQ的Consumer对象,设置NameServer的地址和要消费的Topic,并启动Consumer。 5. 消费消息:Consumer会从RocketMQ服务器中拉取消息,当延迟时间到达时,消费者会收到消息并进行消费。 需要注意的是,RocketMQ延迟消息有一定的误差,实际的延迟时间可能比设置的延迟时间要长。这是因为消息的发送和消费都需要一定的时间,以及网络延迟等因素的影响。 总结起来,实现RocketMQ延迟消息需要搭建RocketMQ环境,创建Producer和Consumer,并在消息中设置延迟时间。通过这些步骤,即可实现RocketMQ延迟消息的功能。 ### 回答3: RocketMQ是一个分布式消息队列系统,可以实现可靠的消息传递和事务消息等。要写一个RocketMQ延迟消息,可以按照以下步骤进行: 1. 首先,确保已经正确设置了RocketMQ的环境和配置,并创建了所需的生产者和消费者。 2. 创建一个Topic(主题)和一个Tag(标签),以便将延迟消息发送到正确的目标。 3. 在生产者端创建消息,并设置延迟时间。可以使用`ScheduleMessage`类的`setDelayTimeLevel`方法来设置延迟级别,取值范围为1-18,代表延迟时间分别为1s、5s、1m、2m等。 4. 将设置好延迟时间的消息发送到指定的Topic和Tag。 5. 在消费者端,通过订阅指定的Topic和Tag,接收延迟消息。 6. 消费者接收到延迟消息后,根据业务需求进行相应的处理。 需要注意的是,RocketMQ延迟消息依赖于Broker的定时任务,默认会每隔1秒扫描所有的延迟消息并发送。 综上所述,以上是一个简单的描述如何写一个RocketMQ延迟消息的步骤。具体实现时,需要按照RocketMQ的API文档和具体业务需求进行操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值