第5章 消息队列的核心机制

Broker 是RocketMQ 的核心,大部分‘重量级”工作都是由Broker 完成的,包括接收Producer 发过来的消息、处理Consumer 的消费消息请求、消息的持久化存储、消息的HA 机制以及服务端过滤功能等。

5.1 消息存储和发送

分布式队列因为有高可靠性的要求,所以数据要通过磁盘进行持久化存储。用磁盘存储消息,速度会不会很慢呢?能满足实时性和高吞吐量的要求吗?

实际上,磁盘有时候会比你想象的快很多,有时候也会比你想象的慢很多,关键在如何使用,使用得当,磁盘的速度完全可以匹配上网络的数据传输速度。目前的高性能磁盘,顺序写速度可以达到600MB/s ,超过了一般网卡的传输速度,这是磁盘比想象的快的地方。但是磁盘随机写的速度只有大概100KB/s,和顺序写的性能相差6000 倍!因为有如此巨大的速度差别,好的消息队列系统会比普通的消息队列系统速度快多个数量级。

举个例子, Linux 操作系统分为“用户态”和“内核态”,文件操作、网络操作需要涉及这两种形态的切换,免不了进行数据复制,一台服务器把本机磁盘文件的内容发送到客户端一般分为两个步骤:

  1. read(file, tmp_buf, len);读取本地文件内容;
  2. write(socket, tmp_ buf, len);将读取的内容通过网络发送出去。

tmp_buf 是预先申请的内存,这两个看似简单的操作,实际进行了4 次数据复制,分别是:从磁盘复制数据到内核态内存,从内核态内存复制到用户态内存(完成了read(file, tmp_buf,len));然后从用户态内存复制到网络驱动的内核态内存,最后是从网络驱动的内核态内存复制到网卡中进行传输(完成write(socket, tmp_buf,len)) 。
通过使用mmap 的方式,可以省去向用户态的内存复制,提高速度。这种机制在Java 中是通过MappedByteBuffer 实现的,具体可以参考Java 7 的文档。
RocketMQ 充分利用了上述特性,也就是所谓的“零拷贝”技术,提高消息存盘和网络发送的速度。

5.2 消息存储结构

RocketMQ 的具体消息存储结构是怎样的呢?如何尽量保证顺序写的呢?先来看看整体的架构图,如图5-1 所示。
RocketMQ 消息的存储是由ConsumeQueue 和CommitLog 配合完成的,消息真正的物理存储文件是CommitLog, ConsumeQueue 是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每个Topic 下的每个Message Queue 都有一个对应的ConsumeQueue 文件。文件地址在 ${$storeRoot}\consumequeue\$ {topicName}\${queueld}\${fileName} 。

图5-1 RocketMQ 的存储结构图

CommitLog 以物理文件的方式存放,每台Broker上的CommitLog 被本机器所有ConsumeQueue 共享,文件地址:${user.home}\store\${commitlog}${fileName} 。在CommitLog 中,一个消息的存储长度是不固定的, RocketMQ采取一些机制,尽量向CommitLog 中顺序写,但是随机读。ConsumeQueue 的内容也会被写到磁盘里作持久存储。

存储机制这样设计有以下几个好处:

  1. CommitLog 顺序写,可以大大提高写人效率。
  2. 虽然是随机读,但是利用操作系统的pagecache 机制,可以批量地从磁盘读取,作为cache 存到内存中,加速后续的读取速度。
  3. 为了保证完全的顺序写,需要ConsumeQueue 这个中间结构,因为ConsumeQueue 里只存偏移量信息,所以尺寸是有限的,在实际情况中,大部分的ConsumeQueue 能够被全部读人内存,所以这个中间结构的操作速度很快,可以认为是内存读取的速度。此外为了保证CommitLog 和ConsumeQueue 的一致性, CommitLog 里存储了Consume Queues 、Message keys、Tag 等所有信息,即使ConsumeQueue 丢失,也可以通过commitLog 完全恢复出来。

如图5-2 所示是一个Broker 在文件系统中存储的各个文件。我们可以看到commitlog 文件夹、consumequeue 文件夹,还有在config 文件夹中Topic 、Consumer 的相关信息。最下面那个文件夹index 存的是索引文件,这个文件用来加快消息查询的速度。

图5 -2 RocketMQ 的Broker 机器磁盘上的文件存储结构

5.3 高可用性机制

RocketMQ 分布式集群是通过Master 和Slave 的配合达到高可用性的,首先说一下Master 和Slave 的区别:在Broker 的配置文件中,参数brokerId的值为0 表明这个Broker 是Master ,大于0 表明这个Broker 是Slave ,同时broker Role 参数也会说明这个Broker 是Master 还是Slave 。Master 角色的Broker 支持读和写, Slave 角色的Broker 仅支持读,也就是Producer 只能和Master 角色的Broker 连接写人消息; Consumer 可以连接Master 角色的Broker ,也可以连接Slave角色的Broker来读取消息。

在Consumer 的配置文件中,并不需要设置是从Master 读还是从Slave读,当Master 不可用或者繁忙的时候, Consumer 会被自动切换到从Slave 读。有了自动切换Consumer 这种机制,当一个Master 角色的机器出现故障后,Consumer 仍然可以从Slave 读取消息,不影响Consumer 程序。这就达到了消费端的高可用性。

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

5.4 同步刷盘和异步刷盘

RocketMQ 的消息是存储到磁盘上的,这样既能保证断电后恢复,又可以让存储的消息量超出内存的限制。RocketMQ 为了提高性能,会尽可能地保证磁盘的顺序写。消息在通过Producer 写人RocketMQ 的时候,有两种写磁盘方式,下面逐一介绍。

  • 异步刷盘方式:在返回写成功状态时,消息可能只是被写人了内存的PAGECACHE ,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写人。
  • 同步刷盘方式:在返回写成功状态时,消息已经被写人磁盘。具体流程是,消息写入内存的PAGECACHE 后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。

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

5.5 同步复制和异步复制

如果一个Broker 组有Master 和Slave, 消息需要从Master 复制到Slave上,有同步和异步两种复制方式。同步复制方式是等Master 和Slave 均写成功后才反馈给客户端写成功状态;异步复制方式是只要Master 写成功即可反馈给客户端写成功状态。

这两种复制方式各有优劣,在异步复制方式下,系统拥有较低的延迟和较高的吞吐量,但是如果Master 出了故障,有些数据因为没有被写人Slave ,有可能会丢失;在同步复制方式下,如果Master 出故障, Slave 上有全部的备份数据,容易恢复,但是同步复制会增大数据写人延迟,降低系统吞吐量。
同步复制和异步复制是通过Broker 配置文件里的brokerRole 参数进行设置的,这个参数可以被设置成ASYNC_MASTER 、SYNC_MASTER 、SLAVE 三个值中的一个。

实际应用中要结合业务场景,合理设置刷盘方式和主从复制方式,尤其是SYNC_FLUSH 方式,由于频繁地触发磁盘写动作, 会明显降低性能。通常情况下,应该把Master 和Save 配置成ASYNC_FLUSH 的刷盘方式,主从之间配置成SYNC_MASTER 的复制方式,这样即使有一台机器出故障, 仍然能保证数据不丢,是个不错的选择。

5.6 本章小结

本章介绍了RocketMQ消息队列实现的难点及核心,即“队列”本身的实现,基于磁盘做一个读写效率高的队列并非易事,实现不好就会使磁盘操作成为整个系统的瓶颈,无法提升系统的吞吐量。RocketMQ 基于“顺序写”“随机读”的原则来设计,利用“零拷贝”技术,克服了磁盘操作的瓶颈。
另一个难点是为了高可用性而设计的主从机制,数据被及时复制到多个机器,这样当一台机器出故障后,整体系统依然可用。这样可靠性和性能能直接有个权衡, RocketMQ 把选择权留给用户,用户根据具体的业务场景来选择要更高的可靠性,还是要更高的效率。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容