1、Topic、MessageQueue、Broker之间的关系
一个Topic可以分布在多个Broker上,所以Topic的数据是分布式存储在多个Broker机器上的
创建Topic的时候要指定MessageQueue的数量。MessageQueue是非常关键的一个数据分片机制,它将Topic的数据拆成了很多个数据分片,然后在每个Broker上存储了一些MessageQueue,通过这个方法,实现了Topic的分布式存储
2、生产者写入
生产者会从NameServer获取Topic的路由数据,知道一个Topic有几个MessageQueue,分别在哪个Broker机器上,现在先假定均匀写入每个MessageQueue。当一个Master Broker挂了,Slave Broker会切换成Master Broker,在切换的过程中,这个Master Broker就会访问失败,所以建议在生产者中设置一个开关:sendLatencyFaultEnable。打开这个开关后,它就有容错机制,比如某次访问某个Broker时间超过500ms,那么在接下来的一定时间内,就不会再访问这个Broker,等这段时间过了,Slave Broker已经切换成Master Broker了,那么又能让生产者访问了
Broker接收到消息后,顺序写入磁盘上名为CommitLog的日志文件,这个CommitLog文件就像LogBack日志文件一样,每个文件最多1G
我们之前提过,Topic在这台Broker机器上会有几个MessageQueue,而一个MessageQueue,也有很多的ConsumeQueue文件,存储目录是{HOME}/stoe/consumequeue/{topicId}/{queueId}/{fileName}下,这个文件存储的是一条信息对应在CommitLog里的物理地址,即偏移量offset,还包括了消息长度、tag hashcode,一条数据是20个字节,每个ConsumeQueue文件保存30万条数据,大概是5.72MB
那么写入CommitLog的速度就直接影响了MQ的性能,所以MQ是基于OS系统的PageCache和顺序写两个机制,来提升写入性能的
顺序写就是上面提到的,在CommitLog的尾部追加数据。而PageCache,是在数据写入CommitLog时,不是直接写入底层的物理磁盘文件的,而是先写入OS的pageCache内存缓存中,然后后续由OS的后台线程自己找一个时间,异步将OS的pageCache缓存中的数据刷新到磁盘文件中
使用PageCache被称作异步刷盘,因为数据写入PageCache就被视作写入成功,当Broker宕机时,这些数据就会消失,因此异步刷盘适合高吞吐量的环境,但存在数据丢失风险。另一种模式被称作同步刷盘,broker必须把消息写入硬盘内,才视作写入成功
总结: 一个Broker上可以有多个Topic,但都存储在统一的CommitLog中。一个Topic分布在多个Broker上,一个Topic分片内有多个MessageQueue,一个MessageQueue对应多个ConsumeQueue文件
在使用DLedger前,RocketMQ的主从Broker模式,如果主Broker挂了,那么只能通过手动方式来进行重启或切换,而引入DLedger就是为了实现自动切换。DLedger是基于Raft协议的,使用DLedger CommitLog替代了原来的CommitLog
Raft协议的多副本同步机制
简单来说,数据同步会分为uncommitted阶段和committed阶段。首先Leader Broker上的DLedger收到一条数据后,会标记为uncommitted状态,然后它会通过自己的DLedgerServer组件把这个uncommitted数据发送给Follower Broker的DLedgerServer。接着Follower Broker的DLedgerServer收到uncommitted消息之后,必须返回一个ack给Leader Broker的DLedgerServer,然后如果Leader Broker收到超过半数的的Follower Broker返回ack后,就将消息标记为committed状态。然后Leader Broker就把committed消息也发给Follower Broker的DLedgerServer,让它们也把消息标记为committed状态
如果Leader Broker挂了,剩下的两个Follower Broker就会重新选举出新的Leader Broker,并且会对没有完成的数据同步进行一些恢复性操作,保证数据不丢失
1、消费者组
给一组消费者起一个名字,它们会消费同一个Topic中的内容。
生产者生产一条消息后,一个消费者组的多台机器都能消费到这条消息吗?在集群模式下,不能,只有其中一台能消费到。在广播模式下,都可以消费到,但广播模式基本不使用。
如下图所示,库存系统和营销系统就是2个消费者组,它们订阅了同一个Topic,所以都可以获取到这个Topic生产的消费信息,但各自系统中只有一个机器可以获取到这条信息。
2、MessageQueue与消费者的关系
大致理解为,让一个Topic内的多个MesageQueue均匀分摊给消费者组内的多个机器去消费。基本原则是一个MessageQueue只能有一个消费者机器消费,但一个消费者机器可以消费多个MesageQueue
3、Broker是如何将消息读取出来给消费者的
假设一台消费者机器要拉去MessageQueue0的消息,并且之前从没拉过,那就从第一条开始拉。于是,Broker找到MessageQueue0对应的ConsumeQueue0,找到第一条消息的offset,去CommitLog中读取出来这条数据
消费者机器获取到这些消息后,会调用我们的回调函数,如图所示,返回消费状态为成功
等我们处理完后,消费者机器会提交我们目前的消费进度到Broker机器上,然后Broker会存储我们的消费进度
4、如果消费者组中有机器宕机了怎么办
会重新给消费者机器分配它们要处理的MessageQueue
1、ConsumeQueue文件也是基于os cache的
ConsumeQueue文件很小,只有几MB,所以它们基本都是放在os cache里的,以此保证消费的高性能
2、CommitLog文件是基于os cache和硬盘一起读的
CommitLog是先写入os cache,再由os把cache中比较旧的数据写入到硬盘,这个过程是不断持续的。因此,如果读取的是刚刚写入的CommitLog数据,那么它们大概率还在os cache中,这样性能就很快。如果读的是较早的数据,那么就都在硬盘中的,这样的效率就会变慢。因此,如果你的消费速度跟上了生产速度,那么基本都是从os cache中取的数据,否则大概率是从磁盘取的。
3、Master Broker什么时候会让你从Slave Broker拿数据
Master broker会根据自身内存的大小,最多可以在os cache存放的数据量,你需要的数据量,来判断出会不会对自己造成比较大的负担,从而决定要不要让你去Slave Broker上拉取数据。
比如你要8万条数据,但Master Broker的os cache最多存3万条,有5万条就要从硬盘上拉取,这样会影响Master Broker的性能,所以会让你去Slave Broker上拉。
传统IO方式读取文件数据,发生了2次数据拷贝
基于mmap+PageCache的读写。比如要写入消息到CommitLog文件里,先把一个CommitLog文件通过MappedByteBuffer的map()函数映射其地址到你的虚拟内存地址。然后对这个MappedByteBuffer执行写入操作,写入的时候会直接进入PageCache,然后过一段时间,由os的线程异步刷入磁盘中。这样就只有一次拷贝过程,就是从PageCache拷入到磁盘空间而已
预热映射机制+文件预热机制
1、Kafka和RabbitMQ有类似RocketMQ的分片机制吗,它们是如何实现的?
2、基于Raft协议的主从复制,会对Leader Broker的TPS产生影响吗?是不是必须所有场景都这么做?
3、消费者是跟少数几台Broker建立连接,还是跟所有Broker建立连接?
跟少数Broker建立长连接,只有要消费指定Topic下MessageQueue对应的Broker时,才会建立连接
4、Kafka和RabbitMQ支持主从架构的读写分离吗?支持Slave Broker的读取吗?
5、比如数据刚刚到Master Broker,还没到Slave Broker,导致Master Broker和Slave Broker数据不一致怎么办?
会存在主从不同步
6、消费处理太慢,会导致你跟不上生产的速度,会导致后续都从硬盘上拉取数据,所以在处理消息的时候,有什么要注意的?