- 消息存储:因为分布式队列有高可靠性的要求,所以数据要进行持久化存储。
- 消息生产到消费的整个流程:
- 消息生产者发送消息到MQ;
- MQ收到消息,将消息持久化,在存储中新增一条记录;
- 返回ACK给生产者;
- MQ push 消息给对应的消费者,然后等待消费者返回ACK;
- 若消息消费者在指定时间内成功返回ack,则MQ认为消息消费成功,在存储中删除消息,即执行第6步;否则认为消息消费失败,会尝试重新push消息,重复执行第4、5、6步骤。
- 消息持久化的存储介质:
-
关系型数据库DB
:Apache下开源的另外一款MQ——ActiveMQ(默认采用的KahaDB做消息存储)可选用JDBC的方式来做消息持久化,通过简单的xml配置信息即可实现JDBC消息存储。由于普通关系型数据库(如:Mysql)在单表数据量达到千万级别的情况下,其IO读写性能往往会出现瓶颈。在可靠性方面,该种方案非常依赖DB,若一旦DB出现故障,则MQ的消息无法落盘存储将导致线上发生故障。 -
文件系统
:目前业界较为常用的几款MQ产品(RocketMQ/Kafka/RabbitMQ)均采用的是消息刷盘至所部署虚拟机/物理机的文件系统来做持久化(刷盘一般可以分为异步刷盘
和同步刷盘
两种模式)。消息刷盘为消息存储提供了一种高效率、高可靠性和高性能的数据持久化方式。除非部署MQ的机器本身或本地磁盘挂了,否则一般是不会出现无法持久化的故障问题。 - 性能对比:文件系统>关系型数据库DB。
-
- 消息存储:若磁盘使用得当,则其速度完全可以匹配上网络数据的传输速度。目前高性能磁盘的顺序写速度可以达到600MB/s, 超过了一般网卡的传输速度。但磁盘随机写的速度大概只有100KB/s,和顺序写的性能相差6000倍!RocketMQ的消息使用
顺序写
,保证了消息的存储速度。 - 消息发送:Linux 操作系统分为用户态和内核态,文件操作、网络操作需要涉及这两种形态的切换,避免不了进行数据复制。一台服务器把本机磁盘文件的内容发送到客户端,一般分为两个步骤:①read:读取本地文件内容;②write:将读取的内容通过网络发送出去。这两个看似简单的操作,实际上进行了4 次数据复制,分别是:
- 从磁盘复制数据到内核态内存;
- 从内核态内存复制数据到用户态内存;
- 从用户态内存复制数据到网络驱动的内核态内存;
- 从网络驱动的内核态内存复制到网卡中进行网络传输。
- 通过使用共享内存mmap的方式,省去了向用户态的内存复制操作,提高了拷贝速度。这种机制在Java中是通过
MappedByteBuffer
来实现的。RocketMQ充分利用了上述特性,也就是所谓的零拷贝
技术,提高了消息存盘和网络发送的速度。注意:采用MappedByteBuffer
这种内存映射的方式有几个限制,其中之一是一次只能映射1.5~2G
的文件至用户态的虚拟内存,这也是为何RocketMQ默认设置单个CommitLog
日志数据文件为1G
的原因了。
- 消息的存储结构:由
ConsumeQueue
和CommitLog
配合完成 的,消息真正的物理存储文件是CommitLog
,ConsumeQueue
是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每个Topic下的每个Message Queue
都有一个对应的ConsumeQueue
文件。 -
CommitLog
:存储消息的元数据。 -
ConsumerQueue
:存储消息在CommitLog的索引。 -
IndexFile
:通过key
或时间区间
来查询消息,且该过程不影响发送与消费消息的主流程。
- 刷盘机制:分布式
同步刷盘
和异步刷盘
。-
同步刷盘
:在返回写成功状态时,消息已被写入磁盘。具体流程是:消息写入内存的PAGECACHE
页缓存后,立刻通知刷盘线程刷盘,待刷盘完成后唤醒等待的线程,返回消息写成功的状态。 -
异步刷盘
:在返回写成功状态时,消息可能只是被写入了内存的PAGECACHE
页缓存,写操作返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写入。 - 采用同步刷盘还是异步刷盘?将Broker配置文件里的
flushDiskType
参数设置为SYNC_FLUSH
,ASYNC_FLUSH
中的一个即可。
-
- RocketMQ分布式集群是通过Master和Slave的配合达到高可用性的。
- Master和Slave的区别:在Broker的配置文件中,参数 brokerId的值为0表明这个Broker是Master,大于0表明这个Broker是 Slave,同时brokerRole参数也会说明这个Broker是Master还是Slave。
- Master角色的Broker支持读和写,Slave角色的Broker仅支持读,也就是 Producer只能和Master角色的Broker连接写入消息;Consumer可以连接 Master角色的Broker和Slave角色的Broker来读取消息。
- 消息发送高可用:在创建Topic时,把Topic的多个Message Queue创建在多个Broker组上(相同Broker名称,不同 brokerId的机器组成一个Broker组),这样当一个Broker组的Master不可用后,其它组的Master仍然可用,Producer仍然可以发送消息。RocketMQ目前还不支持把Slave自动转成Master,若机器资源不足,且需要把Slave转成Master,则要手动停止Slave角色的Broker,更改配置文件,用新的配置文件启动Broker。
- 消息消费高可用:在Consumer的配置文件中,并不需要设置是从Master读还是从Slave 读,当Master不可用或繁忙时,Consumer会被自动切换到从Slave 读。有了自动切换Consumer这种机制,当一个Master角色的机器出现故障后,Consumer仍然可以从Slave读取消息,不影响Consumer程序。这就达到了消费端的高可用性。