rocketmq的broker源码解读一(createBrokerController方法)

1)createBrokerController方法

1.1)与config相关的实例化

BrokerConfig,NettyServerConfig,NettyClientConfig实例化;之所以要实例化后两个,是因为broker同时兼具服务端和客户端的角色;关于后两个的实例化在namesrv和生产者中已讲过,都是公用remoting包下的类;

1.2)实例化BrokerController

1.2.1)保存配置属性,如与broker相关的xxxConfig;
1.2.2)实例化xxxManager类,如ConsumerOffsetManager,TopicConfigManager,ConsumerManager等等;
1.2.3)实例化线程,如PullRequestHoldService,ClientHousekeepingService;
1.2.4)实例化各种线程池队列;
1.2.5)一些其他常用的,包括实例化NotifyMessageArrivingListener,Broker2Client,BrokerOuterAPI,BrokerFastFailure等;其中BrokerOuterAPI的构造方法中会实例化NettyRemotingClient;
还有一些其他的;

1.3)BrokerController#initialize方法

1.3.1)执行各组件的load方法
包括topicConfigManager,consumeOffsetManager,subscriptionGroupManager,consumerFilterManager,这几个类都是ConfigManager的子类;
1.3.2)实例化DefaultMessageStore
若1.3.1中load成功了,则执行到此;
1.3.2.1)实例化AllocateMappedFileService并启动
在getLastMappedFile方法中向requestQueue中添加AllocateRequest实例,随后业务线程阻塞;当allocateMappedFileService线程从requestQueue中取出任务后,会创建MappedFile实例并返回,随后唤醒阻塞的业务线程;
1.3.2.2)实例化CommitLog
a)实例化MappedFileQueue;
b)同步刷盘则实例化GroupCommitService,异步刷盘则实例化 FlushRealTimeService();
c)实例化CommitRealTimeService,当使用堆外内存时,负责将数据由writeBuffer提交至FileChannel的线程;
d)实例化DefalutAppendMessageCallback,负责往mappedFile中追加消息;
1.3.2.3)实例化与consumerQueue,index,ha相关的
如:构建cq文件的线程,cq刷盘线程,清理过期cq文件线程;实例化IndexService并启动;实例化清理过期commitLog线程;实例化HAService;
1.3.2.4)初始化堆外内存
初始化5块大小均为1G的堆外内存,放入Deque中

1.3.3)DefaultMessageStore#load方法
若1.3.1中load成功了,则执行DefaultMessageStore#load方法;依次加载commitLog目录,consumeQueue目录,索引目录,加载完之后,最后进入到恢复阶段;当上次是正常退出时,abort文件会被删除,通过判断该文件是否存在,可以判断出上次broker进程是否是正常退出;

1.3.3.1)CommitLog#load
MappedFileQueue#load方法,将磁盘上指定路径的文件加载到内存;mappedFileQueue的load方法是加载commitLog文件,cq文件的核心方法;
1.3.3.1.1)遍历指定路径下所有文件
创建指定路径下的File实例,获取到指定路径下所有文件,并进行遍历;
1.3.3.1.2)为每个文件创建MappedFile实例
会调用MappedFile类的有参构造方法,其中又会调用MappedFile#init方法;该方法中又会执行:
a)实例化RandomAccessFile创建文件通道fileChannel,
b)调用fileChannel.map方法获取文件内存映射缓冲区mappedByteBuffer;
1.3.3.1.3)初始化每个mappedFile中的位点
设置wrotePosition 和 flushedPosition 这里给的值 都是 mappedFileSize(默认值为1GB),并不是准确值,准确值 需要 recover阶段设置。
1.3.3.1.4)最后将当前 file的mappedFile对象 加入到 list集合管理
1.3.3.2)loadConsumeQueue
若1.3.3.1成功了,则执行此步;加载ConsumeQueue实例管理的mappedFile文件;物理磁盘上的文件结构是有一个consumeQueue文件夹,该文件夹中有一些以topic命名的文件夹,topic文件夹中又有一些以queueId命名的文件夹,每个queueId文件夹下有一些mappedFile文件,这些mappedFile文件由ConsumeQueue实例管理,ConsumeQueue实例通过messageQueueList将所有mappedFile管理起来,每个mappedFile默认可存储30万条消息,每条消息为20字节;
1.3.3.2.1)遍历指定路径下的所有文件
拿到指定路径下的所有文件后,第一层遍历每个topic文件夹,第二层遍历每个topic文件夹中的queueId文件夹,为每个queueId文件夹创建一个ConsumeQueue实例;
1.3.3.2.2)为每个queueId创建ConsumeQueue实例
在ConsumeQueue的构造方法中主要执行以下:
a)实例化MappedFileQueue;
b)执行ByteBuffer.allocate(20)方法初始化byteBufferIndex,即分配20字节大小的ByteBuffer实例给临时缓冲区byteBufferIndex;

1.3.3.2.3)更新consumeQueueTable,执行ConsumeQueue#load方法
a)consumeQueueTable的key为topic,value为map,<queueId,consumeQueue>为其键值对;
b)consumeQueue的load方法,会调用MappedFileQueue#load方法,回到了1.3.3.1中的逻辑;
1.3.3.3)IndexService#load
若上两步都成功了,则执行此处;在1.3.3.2和1.3.3.3之间其实还会实例化StoreCheckPoint,先放着不讲;在…/store目录下,有一个checkPoint文件,创建storeCheckPoint对象,并完成加载checkPoint文件数据,checkpoint文件存储的是commitlog、consumerqueue、index文件的刷盘时间

1.3.3.3.1)遍历指定路径下的所有文件
1.3.3.3.2)为每个文件创建IndexFile实例
在IndexFile的构造方法中,会实例化MappedFile,与IndexHeader;且会保存MappedFile实例中的fileChannel,mappedByteBuffer,hashSlotNum,indexNum;
1.3.3.3.3)IndexFile#load方法
其中会执行IndexHeader#load方法,里边是根据mappedByteBuffer初始化beginTimestamp,endTimestamp,beginPhyOffset,endPhyOffset,indexCount;
1.3.3.3.4)IndexFile#destroy方法
判断如果上次是非正常退出,并且indexFile中最后一条消息的存储时间大于checkPoint时间点,则认为该文件的数据是不安全的,会执行IndexFile#destroy方法,将其所占资源释放,其中会调用mappedFile的destroy方法;
最后将所有indexFile文件加入list集合中;

1.3.3.4)DefaultStoreMessage#recover方法
若1.3.3.1和1.3.3.2执行成功了,则执行此步;先恢复consumeQueue,接着判断若上次是正常退出,则执行正常恢复commitLog,否则执行异常恢复commitLog;consumeQueue的恢复与上次是否正常退出无关;
文件恢复的是为了恢复内存中的如下数据结构
1)CommitLog中包括以topic-queueId为key,offset为value的topicQueueTable,mappedFileQueue;
2)MappedFileQueue中的wroteWhere,flushedWhere,committedWhere,装有mappedFile的list集合mappedFiles;
3)MappedFile中包括wrotePosition,flushedPosition,committedPosition;
4)ConsumeQueue中包括MappedFileQueue,topic,queueId,minLogicOffset,maxPhysicOffset;
5)DefaultMessageStore中包括以topic为key,concurrentHashMap<queueId,ConsumeQueue>为value的consumeQueueTable,consumeQueue文件恢复;
正常恢复从倒数第三个文件开始恢复,每次读取mappedFile内的data,当为有效数据时,继续判断下一条,且offset加20,当加到600万时,代表这个文件到末尾了;开始下一个文件的判断,若判断为无效数据,此时代表此处是当前queueId目录中的最大写位点;此时更新mappedFileQueue中的wroteWhere和flushedWhere的值,以及mappedFile的wrotePosition和flushedPosition的值;
1.3.3.4.1)recoverConsumeQueue方法
根据consumeQueueTable,找到每个topic的每个queueId所对应的ConsumeQueue实例,执行ConsumeQueue#recover方法;该方法的目的是为了恢复当前consumeQueue中mappedFileQueue的位点信息和最后一个mappedFile的位点信息;consumeQueue和commitLog的恢复逻辑一样,不同之处在于判断mappedFile结束的标志不同;个人觉得从最后一个mappedFile文件开始也可以;找到最后一个mappedFile中消息的相对偏移位点;在consumeQueue的每个mappedFile文件中,每条消息20字节,最多插入30万条;
a)从consumeQueue所管理的倒数第三个文件开始遍历,检查每个文件中的消息是否是正常消息,如果是则继续检查下一条,并且每次执行mappedFileOffset+=20,20代表每条消息大小,若没有继续判断下一条消息,则可能是当前mappedFile满了,要换下一个mappedFile继续判断或者结束了,则接下来先判断
b)当mappedFileOffset等于600万字节时,若index++不小于mappedFileQueue中list的长度,则表示是最后一个mappedFile且mappedFile中的内容也读结束了;若index++小于mappedFileQueue中list的长度,则遍历下一个mappedFile文件,归零mappedFileOffset;
c)当mappedFileOffset小于600万字节时,则表示a)中没继续判断下一个消息是因为mappedFile内容读结束了,且为当前mappedFileQueue中的最后一个mappedFile,此时的mappedFileOffset即为当前mfq的最大偏移,最后一个文件的名字加上mappedFileOffset即为当前consumeQueue管理的文件的总偏移量,是对于当前consumeQueue中的所有mappedFile来说的,此值和commitLog无关,此时可以将此总偏移量设置到mappedFileQueue的flushedWhere和wroteWhere中;接着找到mappedFileOffset所在那一个MappedFile实例,更新其flushedPosition和wrotePosition;每次遍历consumeQueue时,更新maxPhysicalOffset为offset+size得到consumeQueue中的消息在commitLog中的绝对偏移量,每次进行比较更新,直到找到所有topic中最大的maxPhysicalOffset,此值表示cq中mappedFile中的消息在commitLog中的位置,并返回给调用者;
在a)中判断消息是否结束的逻辑是下一条消息的offset小于0或者size不大于0;
因为consumeQueue中mappedFile中每条消息的格式是8字节表示offset,4字节表示size,8字节表示tagCode,所以每次循环都会执行byteBuffer.getLong,byteBuffer.getInt,byteBuffer.getLong方法取出值判断,并且byteBuffer上的指针会自动向后推进20字节指向下一条消息的起点,接着通过mappedFileOffset+=20记录每个mappedFile的偏移量,mappedFileOffset每到了一个新的mappedFile时会清零,当推进到空消息时,此时offset或size的返回值为0,可以判定到了当前mappedFile的末尾,且当前mappedFile是当前consumeQueue的mappedFileQueue集合中最后一个文件;
1.3.3.4.2)CommitLog#recoverNormally方法
若上次是正常退出,则会进入到此,恢复逻辑和consumeQueue一样,入参为consumeQueue恢复中的maxPhysicalOffset;
1.3.3.4.2.1)正常恢复commitLog
a)从commitLog所管理的倒数第三个文件开始遍历,检查每个文件中的消息是否是正常消息,如果是则继续检查下一条,并且每次执行mappedFileOffset+=size,这里size代表每条消息大小,不是像consumeQueue中固定,是个不定值,若没有继续判断下一条消息,则可能是当前mappedFile满了,要换下一个mappedFile继续判断或者结束了,则接下来先判断
b)当前消息头为BLANK_MAGIC_CODE且size为0,(相当于cq中mappedFileOffset等于600万字节),若index++不小于mappedFileQueue中list的长度,则表示是最后一个mappedFile且mappedFile中的内容也读结束了;若index++小于mappedFileQueue中list的长度,则遍历下一个mappedFile文件,归零mappedFileOffset;(BLANK_MAGIC_CODE且size为0,表示当前mappedFile文件被写满了,即到了当前文件尾)
c)当前消息头为null,(相当于cq中当mappedFileOffset小于600万字节),则表示a)中没继续判断下一个消息是因为mappedFile内容读结束了,且为当前mappedFileQueue中的最后一个mappedFile,此时的mappedFileOffset即为当前mfq的最大偏移,最后一个文件的名字加上mappedFileOffset即为当前commitLog管理的文件的总偏移量processOffset,此时可以将此总偏移量设置到mappedFileQueue的flushedWhere和wroteWhere中;接着找到mappedFileOffset所在那一个MappedFile实例,更新其flushedPosition和wrotePosition;(当前mappedFile文件没有写满就结束了)
commitLog中mappedFile的存储规则是一个mappedFile最后至少要留8个字节,前四个字节表示剩余空间大小,后四个字节表示文件尾魔数;另外一个mappedFile通常不可能被完全写到最后只剩下8个字节,很可能剩下20个字节时,而下一个消息为200个字节,此时就已经装不下了,所以提前加入8字节的剩余空间大小和文件尾魔数;文件恢复,我们只关心最后一个mappedFile的位点情况,其他mappedFile位点情况不用关心,想想关心干啥呢?难道要记录每个mappedFile到底实际写入了多少字节吗?
下图中是commitLog中mappedFile中消息的格式判断,MESSAGE_MAGIC_CODE表示正常完整的消息;BLANK_MAGIC_CODE表示commitLog中mappedFile的文件尾;通过索引index是否等于list的长度,判断当前mappedFile是否是最后一个mappedFile;为null,表示空消息,到最后了;
还有一种情况是不存在的:即当前mappedFile最后剩余1kb,但没有以文件尾魔数结尾,且存在下一个mappedFile,里边已经追加了新消息。这种情况是不可能存在的;也就是说上一个mappedFile的结尾一定有代表文件尾的魔数;
分一下4种情况
在这里插入图片描述

1.3.3.4.2.2)删除consumeQueue中冗余数据
若maxPhysicalOffset不小于processOffset,则清除consumeQueue中的冗余数据;maxPhysicalOffset是在consumeQueue恢复时,所有topic对应的consumeQueue中在commitLog上最大的偏移量,processOffset是commitLog恢复时最大偏移量;遍历consumeQueueTable,调用ConsumeQueue#truncateDirtyLogicFiles方法;
有几点需要注意:
一是在构建consumeQueue时,消息在不同cq中是分散的,如若1,2,3,4,5连续五条消息在commitLog中是连续存放的,但构建完cq后,如果这五条消息topic相同,则可能会分到同一个queueId下,但若topic不同,则就分到了不同topic中了,但在每个topic的所有cq中,消息的第一个8字节所存储的偏移量offset一定是递增的,也就是说某个topic有三个queueId分别为0,1,2,分别对应三个consumeQueue,此时从queueId为0的第一个消息,到queueId为2的最后一条消息,存储的offset一定是递增的;
删除时,遍历每个topic的每个consumeQueue,从当前cq的最后一个mappedFile文件往前遍历,若mappedFile的第一条消息的offset不小于processOffset,则将当前mappedFile都删掉(这种情况是真删除),接着继续遍历上一个mappedFile;若mappedFile的第一条消息的offset小于processOffset,则继续判断下一条消息,且更新本次位点至当前mappedFile,当出现offset大于processOffset时,退出方法,此时mappedFile中保存的位点后边的数据都将无效;所以consumeQueue中删除冗余数据实质是更新所有topic对应的所有consumeQueue中所有mappedFile的位点,使每个mappedFile中最大位点(包括刷盘位点和写入位点)小于processOffset;(不可能去删除mappedFile中的某条消息)

1.3.3.4.3)CommitLog#recoverAbnormally方法
异常恢复从最后一个文件往前走找到第一个消息存储正常的文件,通过检查消息的存储时间是否小于checkPoint的最小时间点,若小于,则文件属于正常文件,符合恢复要求,即当消息存储的时间戳比commitLog和cq中最小的刷盘时间还要小或相等时,则该文件部分是可靠的,可以从该文件开始恢复;找到正常文件后,接下来逻辑和正常恢复一致,不同的是会执行DefaultMessageStore#doDispatch方法,入参为DispatchRequest实例,去构建consumeQueue文件和索引文件,确保cq,index和commitLog文件是对齐的;
1.3.3.4.4)recoverTopicQueueTable方法
在CommitLog类中保留着一份关于消费者逻辑偏移量的map即topicQueueTable,key为topic-queueId,value为每个queueId所对应的consumeQueue中最大逻辑偏移量除以20(表示存储了多少个20字节的消息);逻辑偏移量指的是consumeQueue中的每个mappedFile中消息的偏移量,是20的整数倍,不是消息的第一个8字节中存储的offset,这个offset指的是在commitLog中的物理偏移量;所以topicQueueTable实际上存储的是每个topic对应的每个queueId存储了多少个20字节的消息;cq中mappedFile的位点设置的就是逻辑偏移量;
1.3.4)实例化NettyRemotingServer和各种线程池
若1.3.1和1.3.3都load成功了,则执行到此;NettyRemotingServer属于公共类,在namesrv中已讲过;另外将fastRemotingServer指向了另一个NettyRemotingServer实例,暂时不知道啥作用;
1.3.5)注册协议处理器
包括sendMessageProcessor,pullMessageProcessor,replyMessageProcessor,queryMessageProcessor,clientManageProcessor,consumerManageProcessor,endTransactionProcessor,adminBrokerProcessor;
1.3.6)initialTransaction
事务相关初始化;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

orcharddd_real

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值