面试必问RocketMQ负载、高可用、NameServer

387 阅读10分钟

笔记大纲

  • NameServer架构设计
  • NameServer路由剔除、故障剔除机制
  • 集群架构模式
  • 同步与刷盘
  • 负载均衡和高可用机制
  • 堆外内存
  • RocketMQ优化
  • 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

1.NameServer架构

image.png NameServer是RocketMQ的大脑,Producer如何知道消息要往那台服务器发送数据?如果Broker宕机了,Producer如何感知?等等这些问题都需要NameServer来解决。 NameServer本身的高可用是通过部署集群的方式来实现,但是NameServer节点之间互不通信,也就是说在某一个时刻NameServer服务器之间不会完全相同,但是不会对消息发送造成影响。RocketMQ之所以这么设计是为了追求简单高效。

2.NameServer路由和注册、剔除机制

2.1 路由元信息

image.png RouteInfoManager路由的元数据信息都是一个个的HashMap。

  - topicQueueTable:Topic消息队列路由信息,消息发送时根据路由表进行负载均衡。
  - brokerAddrTable:Broker基础信息,包含了BrokerName、集群名称、主备Broker地址等。
  - clusterAddrTable:Broker集群信息。
  - brokerLiveTable:Broker状态信息,NameServer每次收到心跳包时会替换该信息。
  - filterServerTable:用于类模式消息过滤,保存Broker上的饿FilterServer列表。

2.2 路由注册

RocketMQ的路由注册是通过Broker和NameServer的心跳功能实现的,Broker启动时向集群中所有的NameServer发送心跳请求,每隔30s向集群中所有NameServer发送心跳包,NameServer收到Broker的心跳包时会更新BrokerLiveTable中的时间戳信息,然后NameServer每隔10s扫描这个BrokerLiveTable,如果连续120s没有更新这个时间戳的Broker,NameServer就会移除该Broker的路由信息同时关闭Socket连接。 NameServer在操作BrokerLiveTable这个Map的时候是通过读写锁来完成的,因为这个状态信息属于是读远大于写的场景,NameServer同一时刻只处理一个Broker的心跳包,所有Broker心跳包串行执行。

2.3 路由剔除

Broker每隔30s向NameServer发送心跳信息,NameServer后台线程每隔10s来检查BrokerLiveTable这个表,如果存在超过120s都未更新时间戳的Broker就会被NameServer认定为故障,被移除掉并关闭网络连接,并同时更新TopicQueueTable、BrokerAddrTable、BrokerLiveTable等。 另外,如果Broker正常关闭退出,也会主动触发路由剔除。

2.4 路由发现

RocketMQ的路由发现是非实时的,当Topic路由出现变动后,NameServer并不会主动通知Producer,而是由客户端定时拉取最新的路由信息。

3.RocketMQ的集群部署模式

3.1 单Master模式

Broker单节点架构,一旦master宕机,整个MQ服务将处于不可用状态。

3.2 多Master模式

多个master节点组成集群,单个master一旦发生了宕机,对MQ服务没有较大影响。 优点:性能高、同一个Topic主题可以分布在多个master,横向拓展好。 缺点:单个master宕机期间,未被消费的消息在节点恢复之前不可用,消息的实时性受影响。

3.3 多master多slave模式(异步复制)

从节点异步复制master节点的消息数据,但是从节点不提供消息的写入。所以从节点对生产者无感,但是在master繁忙的时候替master分担读/消费的任务。 使用异步复制可能会导致数据的丢失。

3.4 多master多slave模式(同步复制-异步刷盘)(推荐)

相比于异步复制,性能损耗在10%,但是能够接收。消息不会丢失,性能也良好。异步刷盘的方式提高了RocketMQ的吞吐量。

3.5 Dledger

RocketMQ在4.5版本推出了Dlegder模式,类似于ZK的集群选举模式,暂不推荐使用。// TODO

4.刷盘策略与数据同步

4.1 同步刷盘

生产者发送的每一条消息都要保存到Broker磁盘成功后才返回高速生产者成功,这种方式不会导致消息丢失,但是性能很差。

4.2 异步刷盘

生产者发送的每一条消息不是立即刷到磁盘,而是缓冲到一个Buffer中就快速返回成功。随后,后台任务再将Buffer中的数据刷回磁盘。(定时刷+阈值触发自动刷)

4.3 同步复制

复制指的是master节点上的消息数据复制给slave从节点的过程。同步复制代表生产者发送的每一条消息都至少同步复制到一个从节点后才返回告诉生产者成功。这种方式可靠性很高,不会造成消息丢失。

4.4 异步复制

相比于同步复制,就是一个异步任务的执行,性能略高,但是存在消息复制阶段丢失问题。

现在RocketMQ推荐的做法是:主从节点采用同步复制、节点内使用异步刷盘的模式组合。

5.Producer发送消息的高可用设计

image.png 1.首先Topic X在两个Master节点的Broker上都有分别4个Message Queue。 2.默认使用轮询的方式进行队列和Broker的选择。例如选中了Broker A的Q4。 3.如果发送成功,则正常返回,结束。 4.如果发送失败就会触发重试机制(消息最大重试次数是2次),并选择使用哪一种重试策略。 5.重试策略有2种:开启和不开启故障延迟机制。(默认不开启) 6.如果不开启故障延迟机制,那么重试发送就会轮询选择刚才失败的那个Broker的下一个队列,例如Broker A的Q1。(这种方式的缺点是有可能重试会再一次失败,因为如果第一次失败了大部分情况是这个Broker有问题了,所以当第二次选择这个Broker的其他队列时,大概率也会失败。) 7.如果开启了故障延迟机制,那么在消息第一次发送失败后就会将该Broker置为不可用列表,转而重新选择Broker。(这种方式的缺点是,一旦所有的Broker都失败了,那么这个客户端将无法发送消息。)

5.1 Broker故障延迟机制(故障规避)

生产者一旦发生了消息发送失败,就会将这个Broker置为不可用,在后续的一段时间内都不会出现在队列、Broker的选择列表上。

6.Consumer消费消息的高可用设计

消息消费者端的消费可以从Master节点读取,也可以从Slave从节点读取。当Master不可用或者系统繁忙的时候就可以从Slave节点读取消息,这样也是高可用的一个设计。 Master服务繁忙:需要拉取的消息已经超过Broker常驻内存的大小,表示服务器繁忙,转而高速消费者从Slave节点读取数据。 Consumer的另一个高可用体现在重试上,一旦某个消息消费失败,就会进入重试队列中,重试队列是基于消费者组Group的,因为不同的Group消费进度互不干扰。 RocketMQ消息的重试时间间隔不是固定的,而是每一次重试都会时间递增。例如第一次重试是10S,下一次可能就是30s,在下一次就是1分钟了。最多重试16次。 当超过16次或指定次数后,仍然没有消费成功,则进入死信队列。死信队列的数据3天后删除。

6.1 Consumer负载

一个Message Queue只能有一个消费者,但是一个消费者可以订阅负载多个消息队列。一般队列数量 >= 消费者数量比较好,如果消费者数量 > 队列数量,即便再加消费者页不起作用。如果消息队列数量 > 消费者数量,那么消费者服务器的启动和退出都会触发重新负载。 Consumer负载有两种模式: image.png image.png

7.Reblance

消费者端触发Rebalance的触发点有3个:

  • 消费者启动
  • 消费者加入or退出
  • 每隔10s定时触发

RocketMQ的Topic的每一个队列有且只能分配一个消费者进行消费。例如,TopicA分布在2个Broker节点上,每一个Broker节点都有默认4个这个TopicA的队列,如果此时只有1个消费者,那么这个消费者就会分摊8个队列的任务,如果此时加入一个消费者,那么每一个消费者就会平均分配4个。当队列数量和消费者数量持平的时候,此时再加入消费者,是不会触发Reblance的,多出来的消费者没有任何任务负担。 另外,Consumer端每隔一段时间就会进行队列的负载重新计算。Consumer拉取消息一次性拉取32条记录放到消费者线程池里进行消费,如果消费者的缓冲区发生了堆积,就会触发Consumer的Pull动作的流控。 Consumer消费者线程池是并发地进行任务池中的任务,如果是顺序消息,那么线程池的线程会和Queue做绑定。 image.png image.png

8.RocketMQ的堆外内存使用

RocketMQ的CommitLog、ConsumQueue等通过mmap技术,当接收到消息时写入内存映射文件,然后消费的时候通过内存映射读取。RocketMQ还提供了另外一套机制来优化效率:堆外内存。 TransientStorePool:RocketMQ创建一个和CommitLog大小一样的ByteBuffer内存缓冲,用来临时存储数据,数据先写入堆外内存,然后后台线程定时从buffer中复制到PageCache中进行持久化刷盘。这种方式需要在RocketMQ的配置文件中手动开启,且Broker模式必须是异步刷盘模式。 两种刷盘方式比较:

image.png

9.消息积压

9.1 Producer太快

     - 控制消息的生产速度
     - 调整业务逻辑,减少没必要的消息
     - 处理逻辑较长和较短的消息不要放在一个队列或者Topic

9.2 Consumer太慢

     - 分析消费逻辑的特点,选择性的增加消费者的线程数
     - 如果队列数量 > consumer数量,可以添加consumer数量来提高消费能力
     - 如果队列数量 <= consumer数量,可以选择增大队列数量,和consumer数量达成一致
     - consumer端修改逻辑,进行消息的搬迁和转移到新的topic中,增大新的队列数量
     - 修改consumer单次拉取消息的数量,默认是32个
     - 丢弃消息、重置消费位点(有时候,这种粗暴的方案或许反而是最高效的)

10.消费幂等

  - 基于数据库去重表,对消息ID、业务唯一标识进行主键、UK的设定来保证幂等。
  - 通过数据库的版本号来做。
  - 分布式锁进行保证。
  - 基于第三方组件的特性,例如ZK的节点唯一性等。

11.RocketMQ调优

11.1 JVM层面优化

     - 关闭偏向锁。
     - 垃圾收集器改为G1

11.2 OS层面优化

     - 修改交换分区的大小。
     - 调大网卡的Ring Buffer大小,避免流量高场景的丢包问题。
     - 中断聚合。
     - 网卡队列CPU多核绑定。
     - pageCache等优化
     - 磁盘优化