RocketMQ原理梳理总结

RocketMQ是阿里对外开源的一种分布式消息中间件,在阿里内部被叫做MetaQ,具有良好的可靠性、可用性以及稳定性

一:概念理解

NameServer

路由服务,类似于kafka中的zk,主要存储了Broker的路由信息,供Producer和Consumer使用,不然Producer怎么知道往哪个Broker发送消息,多个NameSever之间是互相独立且没有通信的,每个NameSever都会保存所有路由信息。

Broker

部署RocketMQ进程的服务被称之为Broker,Broker会接收Producer的消息,持久化到本地,然后Comsumer通Pull的形式进行消息拉取,通常使用集群+主从进行的形式进行部署,主从之间会有数据同步

Producer

生产者,即发送消息的一方,往Master Broker中写入数据

Consumer

消费者,即消费消息的一方,从Master Broker和Slave Broker中获取数据,一般采用群组的形式部署来提高消费性能

Topic

Topic翻译过来就是主题的意思,但它其实是个抽象概念,我们可以理解成数据集合,比如订单系统有一个Topic叫topic_order_info,这个Topic里面就是订单系统投递的订单信息,如果其他系统想要获取订单信息,就可以从这个Topic中获取。

MessageQueue

MessageQueue即消息队列,在创建Topic的时候会让我们指定MessageQueue的数量,即Topic中的消息队列数量。 这些队列会被RocketMQ均衡的分布在不同Broker上,Producer在发送消息时会根据一定策略选择一个消息队列进行发送,这样就可以实现负载均衡和提高吞吐的效果

二:系统架构

了解了RocketMQ的基本概念之后,我们再来看下它的架构,如图所示:

这是一个常见的RocketMq部署架构,所有物理节点都使用集群部署,Broker还是用了主从架构。下图展示了消息在发送和接收中的大致流程:

下面对架构特点进行分析

架构特点

NameServer

首先是NamerServer,它的作用是注册Broker的路由信息,那么它是怎么和Producer、Broker、Consumer之间通信的呢?Broker会和每个NameSever建立TCP长链接,每隔30s发送心跳到所有NamerServer,每个NameSever会每隔10s检查一次有哪些Broker超过120s没有发送心跳的,如果有,就认为该Broker已宕机,从路由信息中删除。Producer如果要向Broker发送信息,必须要知道Broker的路由信息(ip,port),所以需要和NameSever(随机选择)建立一个TCP长链接来获取Broker路由信息,然后就可以根据Topic知道要向哪几台Broker发送消息,再根据负载均衡算法选出一台Broker。

Broker

前面说到Broker是我们部署在服务器上的RocketMQ进程,它起到了存储消息、接收、分发消息的作用, 是Broker的核心,为了保障它的性能和可用性,一般Broker采用主从+集群部署,如下图:

上图中有3个Broker服务,每个Broker又分为主从Broker,主从之间会有数据同步。 一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave, Master也可以部署多个。每个Broker与Name Server集群中的所有节点建立长连接,定时注册Topic信息到所有Name Server。

Producer

Producer随机与NameServer集群中的一个节点建立长连接,定期从NameServer获取Topic路由信息,再和Topic涉及的所有Broker建立长连接,每隔30秒发一次心跳。在Broker端也会每10秒扫描一次当前注册的Producer,如果发现某个Producer超过2分钟都没有发心跳,则断开连接。Producer完全无状态,可集群部署。

Consumer

Consumer也是随机与NameServer集群中的一个节点建立长连接,定期从NameServer取Topic路由信息。 Consumer跟Broker是长连接,会每隔30秒发心跳信息到Broker。Broker端每10秒检查一次当前存活的Consumer,若发现某个Consumer 2分钟内没有心跳,就断开与该Consumer的连接,并且向该消费组的其他实例发送通知,触发该消费者群组和MessageQueue再均衡。 Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。

三:核心设计

生产者如何发消息

Producer首先通过NameSever获取指定Topic的Broker路由信息,比如一个Topic有哪些MessageQueue,MessageQueue在哪几台Broker上,Broker的ip.port等。

上图中,一个Topic有4个MessageQueue,分别分布在2台Broker上,然后Producer会根据MessageQueueSelector的具体实现,或则根据默认策略(均匀写入)把消息发送到其中一个MessageQueue中, 这样就可以把生产者的请求分散给不同的Broker,假设一台Broker可以抗7W并发,那2台Broker就可以实现RocketMq集群抗下每秒10W+的并发。

Broker如果宕机怎么办

如上图中,如果第二个Master Broker宕机了,虽然Slave Broker会自动切换成Master,但在切换的过程中该Broker服务是不可用的。 对于这个问题,在Producer中有一个开关sendLatencyFaultEnable,这个开启后会有一个容错机制,比如某次访问一个Broker有500ms延迟还无法访问,那么接下来就会回避访问该Broker一段时间,比如3000ms内不再访问该Broker,避免消息打到故障的Broker上。

消息持久化

消息持久化是RocketMQ最核心的环节,它决定了生产者消息写入的吞吐量,决定了消息会不会丢失,消费者获取消息的吞吐量。 Broker的消息持久化依赖于两个文件CommitLogConsumeQueue。 当Broker收到一条消息后,首先会把该消息顺序写入磁盘文件CommitLog

 CommitLog的文件结构如下:

这个CommitLog是很多磁盘文件,每个文件最多1GB,当一个文件写满之后,就新建一个。 现在我们的消息已经持久化在了磁盘上,但是有一个问题,当消费者要消费一条消息时,它怎么知道从CommitLog中具体获取哪个消息呢? 这时就用到另一个磁盘文件ConsumeQueue,在Broker中,每个MessageQueue都有一系列ConsumeQueue文件,如: $HOME/store/consumequeue/{topic}/{queueid}/{filename}

queueid就是对应MessageQueue,这个ConsumeQueue文件储存的就是一条消息在CommitLog中的偏移量,看到这里是不是有的懵逼,到底什么意思呢? 其实就是当Broker收到一条消息后,会把消息在CommitLog中的物理位置,也就是一个文件偏移量,记录在对应的MessageQueue的ConsumeQueue文件中。(可以理解为List中的角标索引)

ConsumeQueue文件的记录结构:

ConsumeQueue文件里一条数据是20个字节,最多存放 30W条数据,文件最大5.72MB,存放满了就新建一个。所以实际上Topic的每个 MessageQueue 都对应了 Broker 机器上的多个 ConsumeQueue,保存了 MessageQueue 的消息在 CommitLog 中的物理位置,也就是偏移量。

消费者如何消费消息

消费组

首先我们要了解一个消费组的概念,即一群消费者组成的组,将Topic中的消息队列分散给消费组中的多个消费者,可以提高消息的消费的效率。

上图中,一个Topic有两个消费组来消费,Broker 会分别发送消息到这两个组中,根据订阅规则(集群模式、广播模式)来决定是组里的每台机器都消费还是只有一台来消费,消费者消息消息的过程是先从ComsumerQueue读取消息的offset,然后根据offset到CommitLog读取相应的消息,集群模式消费者会把从CosumerQueue读取的offset保存的Broker,广播模式则存储在本地。

订阅规则

RocketMq有两种订阅规则:集群模式、广播模式。 集群模式就是一条消息一个消费组里只会有一台机器会去消费。 广播模式就是一条消息一个消费组里每一台机器都会去消费。集群模式的原理就是:RocketMq有一个 MessageQueue 分配算法,默认会把 MessageQueue 平均分配给每个Consumer,且一个 MessageQueue 只会分配给一个 Consumer,这是因为消费者每次读取消息的时候,会把最后一个消息的offset保存到Broker存储,下次读取会根据上次保存的offset进行消息读取,如果多个消费读取同一个队列,就会导致偏移量保存被覆盖,进而导致消息被重复度。

消费方式

RocketMq提供了2种消费方式:PUSH和PULL,但实际上,这两种方式本质上是一样的,都是消费者主动去 Broker 拉取消息。 Push模式其实也是消费者主动去 Broker 拉取消息,只不过它实时性很高,就像 Broker 在 Push 一样。它是通过长轮询来实现的,当消费者发起请求到 Broker ,如果没有消息的话,就会把线程挂起(默认15秒),在此期间会有一个后台线程每隔一段时间就去检查一下是否有新的消息,如果有,就唤起线程。

四:特殊场景应用

分布式事务

相对于本地事务,分布式事务是为了满足跨数据源或跨服务的事务场景。上图展示了RocketMQ在分布式事务中的应用,满足CAP定理中的AP,保证最终一致性。主要工作流程就是:本地服务先发一个半消息,这个消息是不能被其他服务进行消费的,然后执行本地事务,根据本地事务执行的成功与否,决定半消息是commit还是rollback,如是commit,这个消息就能被第三方消息进行消费;如果是rollback,这个消息就会被彻底删除。消息的生产服务可能会因为网络原因、应用问题等,导致一直没有对这个半消息进行确认,那么这时候 Broker服务器会定时扫描这些半消息,主动找Producer端查询该消息的状态。当然,什么时候去扫描,包含扫描几次,我们都可以配置。对于消息的消费,在分布式事务的场景中还要考虑幂等性问题,因为RocketMQ、Kafka仅仅保证at-least-once语义,但是无法满足exactly-once(仅一次)语义。比如一个订单消息消息可能被重复消息,这时候可以通过订单处理日志/状态表来对以处理过的订单消息进行过滤。

延时消息

生产者把消息发送到消息队列中以后,并不期望被立即消费,而是等待指定时间后才可以被消费,这类消息通常被称为延迟消息。延时消息是一种很常用的业务场景,在RocketMQ中,只支持特定级别的延迟消息,但是不支持任意时间精度的延迟消息。如果要支持任意时间精度,不能避免在Broker层面做消息排序,再涉及到持久化的考量,那么消息排序就不可避免产生巨大的性能开销。在RocketMQ中,消息延迟级别分别为1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,共18个级别。

如上图所示,延时消息在RocketMQ的工作流程如下:

  1. 如果消息的延迟级别大于0,则表示该消息为延迟消息,修改该消息的主题为SCHEDULE_TOPIC_XXXX,队列Id为延迟级别减1。
  2. 消息进入SCHEDULE_TOPIC_XXXX的队列中。
  3. 定时任务根据上次拉取的偏移量不断从队列中取出所有消息。
  4. 根据延时级别的时间,筛选出已经到期的消息
  5. 根据消息属性重新创建消息,清除延迟级别,恢复原主题和队列Id。
  6. 重新发送消息到原主题的队列中,供消费者进行消费。

顺序消息

对于RocketMQ中的同一个队列,消息的到达顺序决定了消息的消费顺序(FIFO),但是RocketMQ是无法保证全局消息的有序性,原因是如果读写队列有多个,消息就会存储在多个队列中,消费者负载时可能会分配到多个消费队列同时进行消费,多队列并发消费时,无法保证消息消费顺序性。所以本文想说的是针对同一个队列如何实现顺序消息,如下图所示:

队列选择器(MessageQueueSelector)可以帮助我们将顺序消息发送到同一个消息队列上,如图中订单消息所示,我们仅仅在消息发送的过程实现MessageQueueSelector接口的select方法,在select方法中根据订单ID选择消息队列即可,如下面示例代码所示:

保证订单消息发送到同一个消息队列之后,还需要保证顺序消息,在RocketMQ中MessageListenerOrderly自带此实现,如果使用MessageListenerConcurrently则需要使用单线程模式

 五:高性能和可靠性实现

除了通部署方式(主从+集群)对于可靠性和高性能的实现,RocketMQ在单体的代码层面上也做了很多努力,如下:

高性能的实现机制

1.顺序写Commitlog和顺序读ConsumeQueue:

发送消息时,消息是顺序写入CommitLog,区别于Kafka的不同topic分区消息会存储在不同日志文件中,RocketMQ的Commitlog文件存储了多个topic的数据,这样就不会随着topic的增加导致IO竞争带来的吞吐量下降;订阅消息时,消费者端也是顺序读取ConsumeQueue,然后根据其中的起始物理位置的offset到CommitLog读取消息。

2.MessageQueue分布策略:

RockectMQ在创建topic时会制定MessageQueue的数量,MessageQueue类似kafka的分区均匀分布在不同broker上,生产者发送消息时,会根据策略选择MessageQueue,实现负载均衡提高吞吐量

3.零拷贝技术:

RocketMQ默认使用mmap技术来实现消息的读取和写入,减少消息在内存态和用户态之间的拷贝和上下文切换次数

可靠性实现机制

1.同步异步复制

RocketMQ的同步异步复制是通过Broker的配置文件中的brokerRole参数进行设置的,这个参数可以被设置成ASYNC_MASTER、SYNC_MASTER、SLAVE三个值中的一个。其中ASYNC_MASTER表示的是当前Broker的角色是一个异步复制的master,生产者写入消息到Master后无需等待消息复制到Slave即可返回;SYNC_MASTER表示当前的Broker的角色是一个同步复制的master,Master写入完消息之后,需要等待Slave的复制成功,但是这边注意这里只需要有一个Slave复制成功并成功应答即算成功;SLAVE表示的是当前Broker是一个Slave。

2.同步异步刷盘

RocketMQ的同步异步刷盘是通过Broker配置文件里的flushDiskType参数设置的,这个参数被设置成SYNC_FLUSH, ASYNC_FLUSH中的一个,其中SYNC_FLUSH表示同步刷盘,ASYNC_FLUSH表示异步刷盘。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RocketMq是一个纯Java、分布式队列模型的消息中间件,具有高可用、高可靠、高实时、低延迟的特点。其原理主要包括以下几个方面: 1. 生产者和消费者模型:RocketMq采用了生产者和消费者模型,生产者将消息发送到消息队列,消费者从消息队列接收并处理消息。这种模型能够实现解耦,提高系统的可扩展性和可靠性。 2. 消息存储:RocketMq将消息存储在磁盘上,以保证消息的可靠性和高可用性。消息存储采用了顺序写和随机读的方式,以提高存储和读取的效率。 3. 主从复制:RocketMq采用主从复制的方式来保证消息的高可用性。主节点负责接收消息并将其复制到从节点,从节点在主节点失效时接管服务,从而保证消息的可靠性和高可用性。 4. 消息索引:RocketMq通过消息索引来快速查找消息。索引记录了消息的存储位置等信息,使得消息的读取和查找更加高效。 5. 消息推拉模式:RocketMq支持消息的推拉模式,生产者可以选择将消息推送到消费者,也可以让消费者主动拉取消息。这样可以根据实际需求进行灵活的消息传输。 6. 高可用服务:RocketMq提供了多个Broker实例,通过集群方式来提供高可用的服务。当一个Broker实例失效时,其他实例可以接管服务,确保消息的可靠传输。 总结来说,RocketMq原理包括了生产者和消费者模型、消息存储、主从复制、消息索引、消息推拉模式和高可用服务等方面,通过这些机制来实现高可用、高可靠、高实时、低延迟的消息传输。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值