RocketMQ源码解析之broker消息存储流程(Reput ConsumeQueue)

原创不易,转载请注明出处
消息写入流程系列文章列表

  1. RocketMQ源码解析之broker消息存储流程(PutMessage)
  2. RocketMQ源码解析之broker消息存储流程(Reput ConsumeQueue)
  3. RocketMQ源码解析之broker消息存储流程(BuildIndex)


前言

我们在《RocketMQ源码解析之broker消息存储流程(PutMessage)》一文中介绍解析了broker接收到消息,然后写入到commitlog中,将写入结果返回给消息生产者这么一个过程,这个其实就是普通消息send到broker的一个过程了,到这,消息生产者就可以任务它这次send操作完成,但是可以继续往后想想,我们消费者是怎样消费的呢?比如说我一个消费者订阅了 topic是xxx ,难道直接去commitlog里面翻,找找对应的topic?答案是否,我们都知道这个commitlog是个顺序写入,然后所有的topic消息都会追加写到这个commitlog中,关键是你翻也没法翻,下不去手,其实在broker 里面有一个线程,在进行reput操作,将写入到commitlog中的消息给读出来,然后写入到每个topic 每个queueId对应的consumeQueue里面去,接下来我们就看看broker是怎样将commitlog里面的消息写入到consumeQueue里面去的。

1. reput到consumeQueue工作流程介绍

1.1 消息写入commitlog流程回顾

这里我们回顾一下这个消息写入commitlog流程,当broker收到一个消息的时候,对应的processor会看看它是个事务消息不,如果不是的话,直接找到存储器putMessage这个方法,存储器会对这个消息进行各种校验,其中有个比较重要的一个校验就是看看操作系统page cache是否繁忙,这个是否繁忙就是通过写入到page cache时间来算的,然后就是交给commitlog的putMessage方法来进行消息的写入了,先是找到最后一个MappedFile,然后获取一个锁,设置一下获取锁的时间,调用MappedFile的appendMessage方法来进行追加写,最后就是将消息组织成规定的写入格式,追加写到对应的buffer中,更新对应consumeQueueTable里对应的offset,重置获取锁时间为0 ,释放锁资源,判断是否刷盘,进行ha同步。其中这里MappedFile是需要我们注意的,它其实是一个内存映射文件,你写入就相当于写入这个操作系统的page cache中了,如果不是同步刷盘的话,就由操作系统内核线程将脏的page 页刷到硬盘上去,还要注意的是一个commitlog 在真正文件系统中是怎样存储的,它不是一个大文件,而是由多个文件组成的,每个文件名就是那个文件在commitlog起始offset。
在这里插入图片描述

1.2 consumeQueue流程介绍

首先说下这个reput工作,reput其实就是后台一个线程,不停的从commitlog取出消息,然后看看这个消息是哪个topic的哪个queue的,然后找到对应的consumeQueue,将消息在commitlog中的一个offset 还有几个其他信息写入到这个consumeQueue中,reput不光写入consumeQueue这一个事情,还有一个就是为这个消息构建索引,构建索引我们后面篇章会讲解,
这里有个最关键的问题是,我这个reput工作到底从commitlog的哪个位置进行reput,总不能从头开始搞吧,其实在MessageStore 也就是存储器最开始start的时候,会找到一个合适的offset 进行reput工作,到底怎么找呢?就是把所有的一个consumeQueue遍历一遍,然后找到那个最大在commitlog 的offset,是这个样子的,reput是单线程的,一旦决定从哪个offset开始,就会顺着往下找,然后往consumeQueue追加写,找到这个offset之后,就会启动这个reput线程进行工作,反正就是不停的从commitlog中把消息取出来,然后写到对应topic ,queueId下的consumeQueue里面去,具体怎样从commitlog中取出来,存到这个consumeQueue中这个过程我们会在后面进行源码剖析。
在broker 中,一个topic 下面的一个queue id 会对应一个consumeQueue,然后一个consumeQueue 会有一个mappedFileQueue,这mappedFileQueue其实就是一个集合,然后里面有一堆的MappedFile ,然后每个MappedFile映射的文件能存储30w条信息,每条占20个字节,大于400多m空间。

2.reput到consumeQueue源码解析

我们知道存储器MessageStore启动的时候,会给reput这个service设置从哪个offset开始读取并启动reput service
在这里插入图片描述
可以看到 先是找到commitlog中最小的一个offset,然后与每个consumeQueue里面存储的那个最大commitlog offset做比较,选出一个最大的作为起始offset,因为consumeQueue是单线程的,所以找出来那个最大的commitlog offset (其实这里代码称它为PhysicOffset)是最为准确的,毕竟之前的commitlog offset 对应的消息一定被存储到 consumeQueue中了,最后就是设置到reput service 中,启动reput service。

接下来我们看下这个ReputMessageService这个类,ReputMessageService 继承 ServiceThread,这个ServiceThread 抽象类,实现Runnable接口,在这个抽闲类里面是没有重写run 方法的,而是在其子类重写的,然后这个start方法是在这个抽象类里面的,就是创建一个线程,然后将自己传进去,启动线程,ServiceThread就是维护线程启动,唤醒,等待这些状态的,具体实现还是靠子类来,这个抽象类在RocketMQ中使用的地方特别多,其实就是维护一个线程各种状态,执行子类任务。
好了,我们接着来看下ReputMessageService 重写的run 方法
在这里插入图片描述
我们可以看到,它会停那么1ms,然后执行doReput操作,可以看到,这个线程几乎是不停的在运作。我们来看下doReput方法。
在这里插入图片描述
可以看到,先是检查一下reputFromOffset与commitlog里面最小的那个做对比,如果是小于commitlog最小的就设置成commitlog最小offset。
接着就是for循环了,这个doNext就是还需不需要继续循环的一个标识,重要的一个来了,就是从存储器的commitlog中获取这个offset位置的数据,其实就是读取commitlog某个offset位置的数据,这里我们要看下
在这里插入图片描述
先是获取每个MappedFile文件的大小,这个默认是1个G,然后根据这个offset获取一下这个offset所属的MappedFile,接着就是计算一下这个commitlog offset 在 这个mappedFile 一个位置,我们可以看到其实就是offset% 文件大小,最后是从这个MappedFile中获取这个位置的MappedBuffer,这里解释一下,根据offset获取这个offset所在的那个MappedFile这个代码就不用看了,你直接用offset/文件大小能算出来在那个MappedFile中,你要是实在算不出来,你可以commitlog 所有的MappedFile,它里面有个起始的offset,只要在起始offset 与起始offset+文件大小之间就可以了,然后就是从MappedFile中获取pos位置的MappedBuffer我们需要看下
在这里插入图片描述
这个其实就是获取这个MappedFile 中pos之后到MappedFile 现在写到哪的那块byteBuffer,这块属于nio里面ByteBuffer的一些操作,不懂的可以看下相关的api文档。
在这里插入图片描述
用图来表示一下就是这个样子的,这块粉红色的就是byteBufferNew。
好了获取到结果之后我们继续回到这个doReput方法中。
接着就是读取这一块byteBufferNew里面的内容了,可以看到又是一个for循环
在这里插入图片描述
需要了解下这个一行里面的东西,这个其实就是从头开始读,因为commitlog里面存消息的格式是第一个int字段就是这个消息的总大小,它这里其实就是取一条消息的各个字段,然后注意是一条消息,虽然这个byteBuffer中可能有很多消息,这里它只会取一条,那一条消息大小和与它存的那条消息第一个字段做比较,最后将这条消息的一些属性放到这个DispatchRequest里面了,可以看下截图
在这里插入图片描述
好了,在回到doReput方法中,执行了一行DefaultMessageStore.this.doDispatch(dispatchRequest);,这个就是进行分发,看下这个方法
在这里插入图片描述
可以看到非常简单的,就是遍历dispatcherList,其实这个dispatcherList 是在存储器MessageStore对象创建的时候初始化的。
在这里插入图片描述
可以看到默认是有两个,一个是buildConsumeQueue的一个是buildIndex的。本篇就先看看buildConsumeQueue的,下篇文件介绍buildIndex
在这里插入图片描述
这个dispatch还是比较简单,就是判断一下这个消息有关事务的状态,如果是事务半提交阶段,或者是回滚了事务的消息就不做buildConsumeQueue处理,可以想一下,ConsumeQueue是干什么的,它主要是面向消息消费者的,在ConsumeQueue里面的消息都是能被消息消费者看到的,你还没提交事务的消息或者已经回滚了的消息是不可能给消息消费者看到的,看到那岂不是拿去消费了。
好了我们看下这个存储器里面的putMessagePositionInfo 方法。
在这里插入图片描述
首先是找到这个消息对应的consumeQueue,根据topic 与queueId来找
在这里插入图片描述
可以看下,很简单,就是从这个consumeQueueTable 缓存中获取,没有的话就创建。consumeQueueTable 就是个map

private final ConcurrentMap<String/* topic */, ConcurrentMap<Integer/* queueId */, ConsumeQueue>> consumeQueueTable;

接着就是调用consumeQueue对象的putMessagePositionInfoWrapper 方法来处理。
在这里插入图片描述
这个方法这里面其实就是红框里面的重要,for循环是重试的,可以看到重试30次,需要重点关注的是putMessagePositionInfo方法,我们来看下
在这里插入图片描述
putMessagePositionInfo 方法中重要的就是封装要存入consumeQueue的东西,可以看到有 在commitlog中的offset,消息的大小,这个tagcode就是关于tag一些东西,其实就是tag的一个hashcode,可以看到加起来一共是20字节,通过queue offset 计算出在consumeQueue中的一个偏移量。接着就是获取MappedFile,下面这一堆就是校验的了,可以看到最后执行appendMessage操作了,就是把上面组织的内容写入到buffer中
在这里插入图片描述
好了,到这我们reput关于ConsumeQueue的部分就结束了,我们在回到doReput方法中去
在这里插入图片描述
执行完doDispatch后,我们看到是master角色的broker 还需要通知一下messageArrivingListener,这个我们后面再来看,接着就是维护一些offset ,size之类的东西了,如果是slave就是记录一下状态。
好了我们doReput关于consumeQueue的内容我们就解析完成了,下篇我们解析下这个buildIndex,下面附上一张consumeQueue存储格式内容的图,一共是20字节。
在这里插入图片描述

总结

现在总结一下,本篇我们主要是先回顾了一下消息写入commitlog的一个流程,然后介绍了一下reput consumeQueue的一个流程,最后是解析了下reput consumeQueue 相关的源码,其实这个consumeQueue 是给消息消费者用的,可以想一想,消息消费者要消费的时候,会发什么字段给broker ,其实就是就是topic ,queueId,每次获取多少消息,从那个地方开始获取这些字段,前3个都是固定的,假设第4个是一个commitlog offset ,你觉得从commitlog 能很方便找到吗,根本不好找,就算是你消费这维护了commitlog offset ,但是commitlog 中各个topic,queue的消息都会追加写,除非你一条一条的往后翻。
如果是你使用这个consumeQueue ,然后第4个字段是queue offset,你就可以通过这个topic ,queueId快速找到这个consumeQueue,通过queue offset计算出 在consumeQueue的一个偏移量,找到那个MappedFile,然后找到对应条数consumeQueue内容,然后获取到他们在commitlog 的offset ,通过这个offset 跟size 找到对应的消息,这个肯定是要比你从commitlog 的某个位置往后翻要快多了,如果你那个topic消息数量量很小,可以想想一下那个翻commitlog 的一个场景。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页