一、消息存储机制

1.1 介绍

由于消息队列有高可靠性的要求,故要对队列中的数据进行持久化存储。
【RocketMQ】RocketMQ 高可用分析

如图:

  1. 消息生产者先向 MQ 发送消息
  2. MQ 收到消息,将消息进行持久化,并在存储系统中新增一条记录
  3. 返回ACK(确认字符)给生产者
  4. MQ 推送消息给对应的消费者,等待消费者返回ACK(确认字符,确认消费)
  5. 若这条消息的消费者在等待时间内成功返回ACK,则 MQ 认为消息消费成功,删除存储中的消息
  6. 若 MQ 在指定时间内没有收到ACK,则认为消息消费失败,会尝试重新推送消息

 

1.2 存储介质类型和对比

常用的存储类型分为关系型数据库存储分布式KV存储 和 文件系统存储

   关系型数据库存储 分布式KV存储 文件系统存储
简介 选用 JDBC 方式实现消息持久化,只需要简单地配置 xml 即可实现 JDBC 消息存储 kv存储即 Key-Value 型存储中间件,如 Redis 和 RocksDB,将消息存储到这些中间件中 将消息存储到文件系统中
性能 存在性能瓶颈,如mysql在单表数据量达到千万级别的情况下,IO读写性能下降 通过高并发的中间件存储和处理消息,速度必然优于数据库存储方式 将消息刷盘至所部属虚拟化/物理机的文件系统来实现消息持久化,效率更高
可靠性 该方案十分依赖DB,一旦DB出现故障,MQ消息无法落盘存储,从而导致线上故障 相较DB来说更加安全可靠 除非部署 MQ 的机器本身或是本地磁盘挂了,否则一般不会出现无法持久化的问题
项目使用 ActiveMQ Redis、RockDB RocketMQ、Kafaka、RabbitMQ

存储效率:文件系统 > 分布式KV存储 > 关系型数据库DB

开发难度和集成:关系型数据库DB > 分布式KV存储 > 文件系统

 

1.3 消息存储机制

1.3.1 概述

目前的高性能磁盘,顺序写速度可以达到 600MB/s,足以满足一般网卡的传输速度,而磁盘随机读写的速度只有约 100KB/s,与顺序写的性能相差了 6000 倍。故好的消息队列系统都会采用顺序写的方式

 

1.3.2 顺序读写和随机读写对于机械硬盘来说为什么性能差异巨大?

引用自:https://blog.csdn.net/u010087886/article/details/54405934

  顺序读写 随机读写
文件数目 读取一个大文件 读取多个小文件
  比较:明显顺序读写只读取一个大文件,耗时更少。而随机读写需要打开多个文件,写进行多次的训导和旋转延迟,标绿远低于顺序读写
文件预读 顺序读写时磁盘会预读文件,即在读取的起始地址连续读取多个页面,若被预读的页面被使用,则无需再去读取 由于数据不在一起,无法预读
  比较:在大并发的情况下,磁盘预读能够免去大量的读操作,处理速度肯定更快
系统的overhead 只需要找到一个文件,并对这个文件进行属性和权限的检查 需要找到多个文件,并对每个文件进行属性和权限检查
  比较:只寻找一个文件,并确认属性和权限,肯定优于处理多个文件
写入数据 写入新文件时,需要寻找磁盘可用空间 写入新文件时,需要寻找磁盘可用空间。但由于一个文件的存储量更小,这个操作触发频率更多
  比较:顺序读写创建新文件,只需要创建一个大文件就可以用很久,而随机读写可能频繁创建文件。创建文件时需要进行寻找磁盘可用空间等一些列操作,肯定更加耗时

 

1.3.3 消息存储结构和流程

Linux操作系统分为【用户态】和【内核态】,文件操作、网络操作需要涉及这两种形态的切换,免不了进行数据复制。

一台服务器 把本机磁盘文件的内容发送到客户端,一般分为两个步骤:

  1. read;读取本地文件内容
  2. write;将读取的内容通过网络发送出去

这两个看似简单的操作,实际进行了4 次数据复制,分别是:

  1. 从磁盘复制数据到内核态内存
  2. 从内核态内存复 制到用户态内存
  3. 然后从用户态 内存复制到网络驱动的内核态内存
  4. 最后是从网络驱动的内核态内存复 制到网卡中进行传输

【RocketMQ】RocketMQ 高可用分析

通过使用mmap的方式,可以省去向用户态的内存复制,提高速度。这种机制在Java中是通过MappedByteBuffer实现的

RocketMQ充分利用了上述特性,也就是所谓的“零拷贝”技术,提高消息存盘和网络发送的速度。

这里需要注意的是,采用MappedByteBuffer这种内存映射的方式有几个限制,其中之一是一次只能映射1.5~2G 的文件至用户态的虚拟内存,这也是为何RocketMQ默认设置单个CommitLog日志数据文件为1G的原因了

 

1.3.4 消息存储结构

RocketMQ 消息的存储是由 ConsumeQueue 和 CommitLog 配合实现的,CommitLog 负责将消息存储在真正的物理存储文件,而 ConsumeQueue 则是消息的逻辑队列,存储对应消息指向的物理存储的地址。

每个 Topic 下的每个 Message Queue 都有对应的一个 ConsumeQueue 文件

查看文件:

【RocketMQ】RocketMQ 高可用分析

【RocketMQ】RocketMQ 高可用分析

CommitLog:存储消息的元数据,同时也保存了 ConsumerQueue,可以恢复 ConsumerQueue

ConsumerQueue:存储消息在CommitLog的索引,且会被加载到内存中,加快读取速度

IndexFile:为了消息查询提供了一种通过key或时间区间来查询消息的方法,这种通过IndexFile来查找消息的方法不影响发送与消费消息的主流程

 

1.3.5 刷盘机制

RocketMQ 的消息是存储在磁盘上的,这样做有两个优点:

  • 保证断点后恢复
  • 让存储的消息量超出内存的限制

Rocketmq 在保证顺序写时,在通过 Producer 写入 RocketMQ 的时候,支持两种写磁盘方式:同步刷盘和异步刷盘

【RocketMQ】RocketMQ 高可用分析

  同步刷盘 异步刷盘
 消息情况 在返回写成功状态时,消息已经被写入磁盘中。即消息被写入内存的PAGECACHE 中后,立刻通知刷新线程刷盘,等待刷盘完成,才会唤醒等待的线程并返回成功状态 在返回写成功状态时,消息可能只是被写入内存的 PAGECACHE 中。当内存的消息量积累到一定程度时,触发写操作快速写入
性能 需要等待刷盘才能返回结果 消息写入内存后立刻返回结果,吞吐量更高
可靠性 可以保持MQ的消息状态和生产者/消费者的消息状态一致 Master宕机,磁盘损坏的情况下,会丢失少量的消息, 导致MQ的消息状态和生产者/消费者的消息状态不一致

设置方式:

  【RocketMQ】RocketMQ 高可用分析

 

二、高可用

【RocketMQ】RocketMQ 高可用分析

2.1 NameServer 高可用

由于 NameServer 节点是无状态的,且各个节点直接的数据是一致的,故存在多个 NameServer 节点的情况下,部分 NameServer 不可用也可以保证 MQ 服务正常运行

 

2.2 BrokerServer 高可用

RocketMQ是通过 Master 和 Slave 的配合达到 BrokerServer 模块的高可用性的

一个 Master 可以配置多个 Slave,同时也支持配置多个 Master-Slave 组。

当其中一个 Master 出现问题时:

  • 由于Slave只负责读,当 Master 不可用,它对应的 Slave 仍能保证消息被正常消费
  • 由于配置多组 Master-Slave 组,其他的 Master-Slave 组也会保证消息的正常发送和消费

 

2.3 消息消费高可用

Consumer 的高可用是依赖于 Master-Slave 配置的,由于 Master 能够支持读写消息,Slave 支持读消息,当 Master 不可用或繁忙时, Consumer 会被自动切换到从 Slave 读取(自动切换,无需配置)。故当 Master 的机器故障后,消息仍可从 Slave 中被消费

 

2.4 消息发送高可用

在创建Topic的时候,把Topic的多个Message Queue创建在多个Broker组上(相同Broker名称,不同 brokerId的机器组成一个Broker组),这样当一个Broker组的Master不可用后,其他组的Master仍然可用,Producer仍然可以发送消息。 RocketMQ目前还不支持把Slave自动转成Master,如果机器资源不足, 需要把Slave转成Master,则要手动停止Slave角色的Broker,更改配置文 件,用新的配置文件启动Broker。

【RocketMQ】RocketMQ 高可用分析

 

2.5 消息主从复制

2.5.1 同步复制和异步复制

若一个 Broker 组有一个 Master 和 Slave,消息需要从 Master 复制到 Slave 上,有同步复制和异步复制两种方式

  同步复制 异步复制
概念 即等 Master 和 Slave 均写成功后才反馈给客户端写成功状态 只要 Master 写成功,就反馈客户端写成功状态
可靠性 可靠性高,若 Master 出现故障,Slave 上有全部的备份数据,容易恢复 若 Master 出现故障,可能存在一些数据还没来得及写入 Slave,可能会丢失
效率 由于是同步复制,会增加数据写入延迟,降低系统吞吐量 由于只要写入 Master 即可,故数据写入延迟较低,吞吐量较高

 

2.5.2 配置方式

可以对 broker 配置文件里的 brokerRole 参数进行设置,提供的值有:

ASYNC_MASTER:异步复制

SYNC_MASTER:同步复制

SLAVE:表明当前是从节点,无需配置 brokerRole

 

2.5.3 实际应用

在实际应用中,由于同步刷盘方式会频繁触发磁盘写操作,明显降低性能,故通常配置为:

刷盘方式:ASYNC_FLUSH(异步刷盘)

主从复制:SYNC_MASTER(同步复制)

异步刷盘能够避免频繁触发磁盘写操作,除非服务器宕机,否则不会造成消息丢失。

主从同步复制能够保证消息不丢失,即使 Master 节点异常,也能保证 Slave 节点存储所有消息并被正常消费掉。

 

三、负载均衡

3.1 Provider 负载均衡

3.1.1 概述

在实例发送消息时,默认会轮询所有订阅了改 Topic 的 broker 节点上的 message queue,让消息平均落在不同的 queue 上,而由于这些 queue 散落在不同的 broker 节点中,即使某个 broker 节点异常,其他存在订阅了这个 Topic 的 message queue 的 broker 依然能消费消息

【RocketMQ】RocketMQ 高可用分析

 

3.1.2 配置

打开 rocketmq-console,在 Topic 中新建主题,并指定要在哪些 broker 内订阅这些 Topic

【RocketMQ】RocketMQ 高可用分析

【RocketMQ】RocketMQ 高可用分析

发消息时的数据结果,可以看到 RocketMQ 集群都在同时消费这些消息

【RocketMQ】RocketMQ 高可用分析

可以看到不同的队列在处理这些消息

【RocketMQ】RocketMQ 高可用分析

 

3.2 Customer 负载均衡

3.2.1 集群模式

在集群消费模式下,存在多个消费者同时消费消息,同一条消息只会被某一个消费者获取。即消息只需要被投递到订阅了这个 Topic 的消费者Group下的一个实例中即可,消费者采用主动拉去的方式拉去并消费,在拉取的时候需要明确指定拉取那一条消息队列中的消息。

每当有实例变更,都会触发一次所有消费者实例的负载均衡,这是会按照queue的数量和实例的数量平均分配 queue 给每个实例。

【RocketMQ】RocketMQ 高可用分析

注意:

1)在集群模式下,一个 queue 只允许分配给一个消费者实例,这是由于若多个实例同时消费一个 queue 的小,由于拉取操作是由 consumer 主动发生的,可能导致同一个消息在不同的 consumer 实例中被消费。故算法保证了一个 queue 只会被一个 consumer 实例消费,但一个 consumer 实例能够消费多个 queue

2)控制 consumer 数量,应小于 queue 数量。这是由于一个 queue 只允许分配给一个 consumer 实例,若 consumer 实例数量多于 queue,则多出的 consumer 实例无法分配到 queue消费,会浪费系统资源

 

3.2.2 广播模式

广播模式其实不是负载均衡,由于每个消费者都能够拿到所有消息,故不能达到负载均衡的要求

【RocketMQ】RocketMQ 高可用分析

 

相关文章:

  • 2021-09-17
  • 2022-12-23
  • 2021-07-27
  • 2021-09-07
  • 2022-12-23
  • 2021-11-26
  • 2021-11-03
猜你喜欢
  • 2018-05-05
  • 2018-05-06
  • 2022-12-23
  • 2021-04-26
  • 2021-06-09
  • 2021-07-07
相关资源
相似解决方案