一、消息的生产过程
1、Producer可以将消息写入到某Brocker中的某个Queue中,经历了如下过程:
- Producer先向NameServer发送获取消息Topic的路由信息请求;
- NameServer返回该topic的路由表以及Broker列表;
- Producer根据代码中指定的Queue列表选出一个队列,用于后续存储消息;
- Producer对消息进行处理,选择出的Queue所在的Broker发出RPC请求,将消息发送到选择的Queue中。
2、涉及到的名词解释:
- 路由表:实际是map,key为topic名称,value为QueueData实例列表。 QueueData并不是一个Queue对应一个QueueData,而是一个Broker中该Topic的所有Queue对应一个QueueData。简单来说,路由表key为topic,value为所有涉及该topic的BrokerName列表。
- Broker列表:实际也是一个map。key为brokerName,value为BrokerData。一套brokerName名称相同的Master-Slave小集群对应一个BrokerData。BrokerData中包含brokerName及一个map。该map的key为brokerId,value为该broker对应的地址。brokerId为0表示该broker为Master,非0为Slave。
3、对于一个无序消息,其Queue选择算法,也称为消息投递算法。常见的有两种:
- 轮训算法
默认选择算法,保证每一个Queue均可以获取到消息。容易出现消息积压。 - 最小投递延迟算法
每次消息投递记录时间延迟,将消息投递给上次时间最小的Queue,如果延迟相同,采用轮训算法。容易出现Queue分配不均匀,投递延迟小的Queue其可能会存在大量消息,而对该Queue的消费者压力会增大,降低消息的消费能力,可能会导致MQ中消息堆积。
4、消息的存储:
- 主流的消息存储方式:分布式KV存储、文件系统存储、关系型数据库。
- Rocket MQ中的消息存储在本地文件系统中,这些相关文件默认在当前用户主目录下的store目录中。
涉及到的名词解释:
- abort:该文件在Broker启动后自动创建,正常关闭Broker,该文件会自动消失。若在没有启动Broker的情况下,发现这个文件是存在的,则说明之前的Broker的关闭事非正常关闭。
- checkpoint:存储着commitlog、consumequeue、index文件的最后刷盘时间戳。
- commitlog:存放commitlog文件,而消息写在commitlog文件中的
- config:存放着Broker运行期间的一些配置数据。
- consumequeue:其中存放着 consumequeue 文件。
5.1 index:其中存放着消息索引文件indexFile。
5.2 lock:运行期间使用到的全局资源锁。
5.3 commitlog文件(mappedFile)
消息落盘位置,大小为1G,多余部分新建文件,文件名为20位十进制数构成,表示当前文件第一条起始偏移量。
一个Broker中有一个commitlog目录,所有的mappedFile文件都存放在该目录中。无论当前Broker存放多少topic消息,这些消息都被顺序写入mappedFile文件中。顺序存取效率高于随机存取。
消息单元
5.4 consumequeue:消费队列
为了提高效率,会为每个Topic在~/store/consumequeue中创建一个目录,目录名为topic名称。在该Topic目录下,会再为每个该Topic的Queue建立一个目录,目录名为queueId。每个目录中存放若干consumequeue文件,consumequeue文件是commitlog的索引文件,可以根据consumequeue定位到具体的消息。
Consumequeue文件名也是由20位数字构成,表示当前文件的第一个索引条目的起始位置偏移量。与mappedFile文件名不同的是,后续文件名是固定的,因为consumequeue文件大小是固定不变的。
索引条目:消息在mappedFile文件中的偏移量CommitLogOffset、消息长度、消息Tag的hashcode值。这三个属性占20个字节,所以每个文件大小都是固定的30w20字节。
5.5 对文件读写
消息写入:
Broker根据queueId获取到消息对应索引条目要在consumequeue目录写入偏移量(QueueOffset)。
将queueId、queueOffset等数据,与消息一起封装为消息单元。
将消息单元写入commit log。
同时形成索引目录。
将消息索引条目分发到相应的consumequeue。
消息拉取:
Consumer向Broker发送拉取请求,其中会包含要拉取消息的Queue、消息offset及消息Tag。
消息offset=消费offset+1
Broker计算在该consume queue中的queue Offset。
queueoffset=消息offset20字节
从该queue Offset处开始向后查找第一个指定Tag的索引条目。
解析该索引条目的前8个字节,即可定位到该消息在commitlog中的commitlogoffset。
从对应commitlogoffset中读取消息单元,并发送给Consumer。
性能:RocketMQ对文件的读写通过mmap零拷贝进行,文件操作转换为对内存地址进行操作,提高文件的读写效率。
consumequeue中的数据顺序存放,引入了pageCache的预读取机制,使得对consumequeue文件的读取接近内存读取,即使在消息堆积的情况下也不会影响性能。
PageCache预读取机制:页缓存机制,加速对文件的读写操作。
写操作:OS会将数据写入到PageCache中,以异步方式由pdflush内核线程将Cache中的数据刷盘到物理磁盘。
读操作:首先从PageCache中读取,没有命中在从物理磁盘上加载该数据到PageCache的同时,也会顺序对其相邻数据块中的数据进行预读取。
RocketMQ中会影响性能的是对commitlog文件的读取,因为是随机读取影响性能。如果选择合适的IO调度算法,比如设置调度算法为Deadline,随机性也会有所提升。 - index File
store文件下index子目录中的index File,可以根据key进行对消息的查询。index File中的索引数据是在包含key的消息被发送到Broker时写入的,没有key不写入。
索引条目结构:时间戳命名,文件由indexHeader,slots槽位,indexes索引数据组成。每个index File包含500w个slot槽,每个slot槽挂载很多的index索引单元。