1.高级功能
1.1消息存储
1.1.1存储介质
1.1.2性能对比
文件系统>关系型数据库DB
1.1.3消息的存储与发送
1)消息存储
磁盘如果使用得当,磁盘的速度完全可以匹配上网络的数据传输速度。目前的高性能磁盘,顺序写速度可以达到600MB/s,
超过一般网卡的传输速度。但是磁盘的随机写的速度只有大概100kb/s,和顺序写性能相差6000倍!因为有如此的速度差别,
好的消息队列系统会比普通的消息队列系统速度快多个数量级。RocketMQ的消息顺序写,保证了消息存储速度。
2)消息发送
Linux操作系统分为【用户态】和【内核态】,文件操作、网络操作需要涉及这两种形态的切换,免不了进行数据复制。
一台服务器把本地磁盘文件的内容发送客户端,一般分为两个步骤:
read: 读取本地文件内容
write:将读取的内容通过网络发送出去。
这两个看似简单的操作,实际上进行了4次数据复制,分别为:
从磁盘复制数据到内核状态
从内核态内存复制到用户态内存
然后从用户态内存复制到网络驱动的内核态内存
最后是从网络驱动内核态n内存复制到网卡进行传输
通过使用mmap的方式,可以省去向用户态的内存复制,提高速度。这种机制再Java中通过MapperByteBuffer实现的
RocketMQ充分使用了上述特性,也就是所谓的“零拷贝”技术,提高消息存盘和网络发送的速度。
采用MapperByteBuffer这种内存映射的方式有几个限制,其中之一是一次只能映射1.5~2G的文件至用户态虚拟内存,
这就是为何RocketMQ默认设置单个CommitLog日志数据文件为1G的原因。
1.1.4消息存储结构
RocketMQ消息存储都是由ConsumerQueue和CommitLog配合完成,消息真正的物理存储文件时commitLog,ComsumerQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每个topic下每个MessgaeQueue都有一个对应ConsumerQueue文件
CommitLog: 存储消息的元数据
ConsumerQueue:存储消息再CommitLog的索引
IndexFile:为了消息查询提供了一种通过key或时间区间来查询消息的方法,这种通过IndexFile来查找消息的方法不影响发送与消费消息的主流程
1.1.5刷盘机制
RocketMQ的存储读写是基于JDK NIO的内存映射机制的,消息存储时首先将消息追加到内存中。在根据不同的刷盘策略在不同的时间进行刷盘。如果是同步刷盘,消息追加到内存后,将同步调用MappedByteBuffer的force()方法,同步等待刷盘结果,进行刷盘结果返回。如果是异步刷盘,在消息追加到内存后立刻,不等待刷盘结果立刻返回存储成功结果给消息发送端。RocketMQ使用一个单独的线程按照一个设定的频率执行刷盘操作。通过在broker配置文件中配置flushDiskType来设定刷盘方式,ASYNC_FLUSH(异步刷盘)、SYNC_FLUSH(同步刷盘)。默认为异步刷盘。本次以Commitlog文件刷盘机制为例来讲解刷盘机制。Consumequeue、IndexFile刷盘原理和Commitlog一样。索引文件的刷盘机制并不是采取定时刷盘机制,而是每更新一次索引文件就会将上一次的改动刷写到磁盘。
刷盘服务是将commitlog、consumequeue两者中的MappedFile文件中的MappedByteBuffer或者FileChannel中的内存中的数据,刷写到磁盘。还有将IndexFile中的MappedByteBuffer(this.mappedByteBuffer = this.mappedFile.getMappedByteBuffer())中内存的数据刷写到磁盘。
TransientStorePool的作用
TransientStorePool 相当于在内存层面做了读写分离,写走内存磁盘,读走pagecache,同时最大程度消除了page cache的锁竞争,降低了毛刺。它还使用了锁机制,避免直接内存被交换到swap分区
1.2高可用性机制
RocketMQ分布式集群是通过Master和Slave的配合达到高可用性的。 Master和Slave的区别:
在Broker的配置文件中,参数brokerId的值为0表明这个Broker是Master,
大于0表明这个Broker是Slave,
brokerRole参数也说明这个Broker是Master还是Slave。 (SYNC_MASTER/ASYNC_MASTER/SALVE)
Master角色的Broker支持读和写,Slave角色的Broker仅支持读。
Consumer可以连接Master角色的Broker,也可以连接Slave角色的Broker来读取消息
消息消费高可用
在Consumer的配置文件中,并不需要设置是从Master读还是从Slave 读,当Master不可用或者繁忙的时候,Consumer会被自动切换到从Slave 读。有了自动切换Consumer这种机制,当一个Master角色的机器出现故障后,Consumer仍然可以从Slave读取消息,不影响Consumer程序。这就达到了消费端的高可用性。
消息发送高可用
在创建Topic的时候,把Topic的多个Message Queue创建在多个Broker组上(相同Broker名称,不同brokerId的机器组成一个Broker组),这样既可以在性能方面具有扩展性,也可以降低主节点故障对整体上带来的影响,而且当一个Broker组的Master不可用后,其他组的Master仍然可用,Producer仍然可以发送消息的。
这种早期方式在大多数场景下都可以很好的工作,但也面临一些问题。 比如,在需要保证消息严格顺序的场景下,由于在主题层面无法保证严格顺序,所以必须指定队列来发送消息,对于任何一个队列,它一定是落在一组特定的主从节点上,如果这个主节点宕机,其他的主节点是无法替代这个主节点的,否则就无法保证严格顺序。
在这种复制模式下,严格顺序和高可用只能选择一个
RocketMQ 在 2018 年底迎来了一次重大的更新,引入 Dledger,增加了一种全新的复制方式。RocketMQ 引入 Dledger,使用新的复制方式,可以很好地解决这个问题。Dledger 在写入消息的时候,要求至少消息复制到半数以上的节点之后,才给客户端返回写入成功,并且它是支持通过选举来动态切换主节点的
举例: 假如有3个节点,当主节点宕机的时候,2 个从节点会通过投票选出一个新的主节点来继续提供服务,相比主从的复制模式,解决了可用性的问题。 由于消息要至少复制到 2 个节点上才会返回写入成功,即使主节点宕机了,也至少有一个节点上的消息是和主节点一样的。 Dledger在选举时,总会把数据和主节点一样的从节点选为新的主节点,这样就保证了数据的一致性,既不会丢消息,还可以保证严格顺序
存在问题: 当然,Dledger的复制方式也不是完美的,依然存在一些不足:
比如,选举过程中不能提供服务。
最少需要 3 个节点才能保证数据一致性,3 节点时,只能保证 1 个节点宕机时可用,如果 2个节点同时宕机,即使还有 1 个节点存活也无法提供服务,资源的利用率比较低。
另外,由于至少要复制到半数以上的节点才返回写入成功,性能上也不如主从异步复制的方式快。