大家好,我是三友~~

这篇文章我准备来聊一聊RocketMQ音讯的终身。

不知你是否跟我相同,在运用RocketMQ的时分也有许多的疑惑:

  • 音讯是怎么发送的,行列是怎么挑选的?
  • 音讯是怎么存储的,是怎么确保读写的高性能?
  • RocketMQ是怎么完成音讯的快速查找的?
  • RocketMQ是怎么完成高可用的?
  • 音讯是在什么时分会被清除?

本文就经过探讨上述问题来探秘音讯在RocketMQ中时刻短而又精彩的终身。

假如你还没用过RocketMQ,能够看一下这篇文章 RocketMQ保姆级教程

中心概念

  • NameServer:能够了解为是一个注册中心,首要是用来保存topic路由信息,管理Broker。在NameServer的集群中,NameServer与NameServer之间是没有任何通讯的。
  • Broker:中心的一个人物,首要是用来保存音讯的,在发动时会向NameServer进行注册。Broker实例能够有许多个,相同的BrokerName能够称为一个Broker组,每个Broker组只保存一部分音讯。
  • topic:能够了解为一个音讯的调集的名字,一个topic能够散布在不同的Broker组下。
  • 行列(queue) :一个topic能够有许多行列,默许是一个topic在同一个Broker组中是4个。假如一个topic现在在2个Broker组中,那么就有或许有8个行列。
  • 生产者:生产音讯的一方便是生产者
  • 生产者组:一个生产者组能够有许多生产者,只需求在创立生产者的时分指定生产者组,那么这个生产者就在那个生产者组
  • 顾客:用来消费生产者音讯的一方
  • 顾客组:跟生产者相同,每个顾客都有地点的顾客组,一个顾客组能够有许多的顾客,不同的顾客组消费音讯是互不影响的。

音讯诞生与发送

咱们都知道,音讯是由事务体系在运行进程产生的,当咱们的事务体系产生了音讯,咱们就能够调用RocketMQ供给的API向RocketMQ发送音讯,就像下面这样

DefaultMQProducerproducer=newDefaultMQProducer("sanyouProducer");
//指定NameServer的地址
producer.setNamesrvAddr("localhost:9876");
//发动生产者
producer.start();
//省掉代码。。
Messagemsg=newMessage("sanyouTopic","TagA","三友的java日记".getBytes(RemotingHelper.DEFAULT_CHARSET));
//发送音讯并得到音讯的发送结果,然后打印
SendResultsendResult=producer.send(msg);

虽然代码很简单,咱们不经意间或许会考虑如下问题:

  • 代码中只设置了NameServer的地址,那么生产者是怎么知道Broker地点机器的地址,然后向Broker发送音讯的?
  • 一个topic会有许多行列,那么生产者是怎么挑选哪个行列发送音讯?
  • 音讯一旦发送失利了怎么办?

路由表

当Broker在发动的进程中,Broker就会往NameServer注册自己这个Broker的信息,这些信息就包含自身地点服务器的ip和端口,还有便是自己这个Broker有哪些topic和对应的行列信息,这些信息便是路由信息,后边就一致称为路由表。

RocketMQ消息短暂而又精彩的一生

Broker向NameServer注册

当生产者发动的时分,会从NameServer中拉取到路由表,缓存到本地,一起会开启一个守时任务,默许是每隔30s从NameServer中从头拉取路由信息,更新本地缓存。

行列的挑选

好了经过上一节咱们就了解了,原来生产者会从NameServer拉取到Broker的路由表的信息,这样生产者就知道了topic对应的行列的信息了。

可是因为一个topic或许会有许多的行列,那么应该将音讯发送到哪个行列上呢?

RocketMQ消息短暂而又精彩的一生

面临这种状况,RocketMQ供给了两种音讯行列的挑选算法。

  • 轮询算法
  • 最小投递推迟算法

轮询算法 便是一个行列一个行列发送音讯,这些就能确保音讯能够均匀散布在不同的行列底下,这也是RocketMQ默许的行列挑选算法。

可是因为机器性能或许其它状况或许会呈现某些Broker上的Queue或许投递推迟较严峻,这样就会导致生产者不能及时发音讯,形成生产者压力过大的问题。所以RocketMQ供给了最小投递推迟算法。

最小投递推迟算法 每次音讯投递的时分会统计投递的时刻推迟,在挑选行列的时分会优先挑选投递推迟时刻小的行列。这种算法或许会导致音讯散布不均匀的问题。

假如你想启用最小投递推迟算法,只需求按如下办法设置一下即可。

producer.setSendLatencyFaultEnable(true);

当然除了上述两种行列挑选算法之外,你也能够自定义行列挑选算法,只需求完成MessageQueueSelector接口,在发送音讯的时分指定即可。

SendResultsendResult=producer.send(msg,newMessageQueueSelector(){
@Override
publicMessageQueueselect(List<MessageQueue>mqs,Messagemsg,Objectarg){
//从mqs中挑选一个行列
returnnull;
}
},newObject());

MessageQueueSelector RocketMQ也供给了三种完成

  • 随机算法
  • Hash算法
  • 依据机房挑选算法(空完成)

其它特殊状况处理

发送反常处理

总算,不论是经过RocketMQ默许的行列挑选算法也好,又或是自定义行列挑选算法也罢,总算挑选到了一个行列,那么此刻就能够跟这个行列地点的Broker机器树立网络连接,然后经过网络恳求将音讯发送到Broker上。

可是不幸的事产生了,Broker挂了,又或许是机器负载太高了,发送音讯超时了,那么此刻RockerMQ就会进行重试。

RockerMQ重试其实很简单,便是从头挑选其它Broker机器中的一个行列进行音讯发送,默许会重试两次。

当然假如你的机器比较多,能够将设置重试次数设置大点。

producer.setRetryTimesWhenSendFailed(10);
音讯过大的处理

一般状况下,音讯的内容都不会太大,可是在一些特殊的场景中,音讯内容或许会呈现很大的状况。

遇到这种音讯过大的状况,比方在默许状况下音讯大小超越4k的时分,RocketMQ是会对音讯进行压缩之后再发送到Broker上,这样在音讯发送的时分就能够削减网络资源的占用。

音讯存储

好了,经过以上环节Broker总算成功接纳到了生产者发送的音讯了,可是为了能够确保Broker重启之后音讯也不丢掉,此刻就需求将音讯耐久化到磁盘。

怎么确保高性能读写

因为涉及到音讯耐久化操作,就涉及到磁盘数据的读写操作,那么怎么完成文件的高性能读写呢?这儿就不得不说到的一个叫零复制的技能。

传统IO读写办法

说零复制之前,先说一下传统的IO读写办法。

比方现在需求将磁盘文件经过网络传输出去,那么整个传统的IO读写模型如下图所示

RocketMQ消息短暂而又精彩的一生

传统的IO读写其实便是read + write的操作,整个进程会分为如下几步

  • 用户调用read()办法,开始读取数据,此刻产生一次上下文从用户态到内核态的切换,也便是图示的切换1
  • 将磁盘数据经过DMA复制到内核缓存区
  • 将内核缓存区的数据复制到用户缓冲区,这样用户,也便是咱们写的代码就能拿到文件的数据
  • read()办法回来,此刻就会从内核态切换到用户态,也便是图示的切换2
  • 当咱们拿到数据之后,就能够调用write()办法,此刻上下文会从用户态切换到内核态,即图示切换3
  • CPU将用户缓冲区的数据复制到Socket缓冲区
  • 将Socket缓冲区数据复制至网卡
  • write()办法回来,上下文从头从内核态切换到用户态,即图示切换4

整个进程产生了4次上下文切换和4次数据的复制,这在高并发场景下肯定会严峻影响读写性能。

所以为了削减上下文切换次数和数据复制次数,就引入了零复制技能。

零复制

零复制技能是一个思想,指的是指计算机履行操作时,CPU不需求先将数据从某处内存复制到另一个特定区域。

完成零复制的有以下几种办法

  • mmap()
  • sendfile()
mmap()

mmap(memory map)是一种内存映射文件的办法,即将一个文件或许其它方针映射到进程的地址空间,完成文件磁盘地址和进程虚拟地址空间中一段虚拟地址的逐个对映关系。

简单地说便是内核缓冲区和运用缓冲区共享,然后削减了从读缓冲区到用户缓冲区的一次CPU复制。

比方依据mmap,上述的IO读写模型就能够变成这样。

RocketMQ消息短暂而又精彩的一生

依据mmap IO读写其实就变成mmap + write的操作,也便是用mmap代替传统IO中的read操作。

当用户建议mmap调用的时分会产生上下文切换1,进行内存映射,然后数据被复制到内核缓冲区,mmap回来,产生上下文切换2;随后用户调用write,产生上下文切换3,将内核缓冲区的数据复制到Socket缓冲区,write回来,产生上下文切换4。

整个进程比较于传统IO首要是不必将内核缓冲区的数据复制到用户缓冲区,而是直接将数据复制到Socket缓冲区。上下文切换的次数依然是4次,可是复制次数只需3次,少了一次CPU复制。

Java中,供给了相应的api能够完成mmap,当然底层也仍是调用Linux体系的mmap()完成的

FileChannelfileChannel=newRandomAccessFile("test.txt","rw").getChannel();
MappedByteBuffermappedByteBuffer=fileChannel.map(FileChannel.MapMode.READ_WRITE,0,fileChannel.size());

如上代码拿到MappedByteBuffer,之后就能够依据MappedByteBuffer去读写。

sendfile()

sendfile()跟mmap()相同,也会削减一次CPU复制,可是它一起也会削减两次上下文切换。

RocketMQ消息短暂而又精彩的一生

如图,用户建议sendfile()调用时会产生切换1,之后数据经过DMA复制到内核缓冲区,之后再将内核缓冲区的数据CPU复制到Socket缓冲区,最终复制到网卡,sendfile()回来,产生切换2。

同样地,Java也供给了相应的api,底层仍是操作体系的sendfile()

FileChannelchannel=FileChannel.open(Paths.get("./test.txt"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//调用transferTo办法向方针数据传输
channel.transferTo(position,len,target);

经过FileChannel的transferTo办法即可完成。

transferTo办法(sendfile)首要是用于文件传输,比方将文件传输到另一个文件,又或许是网络。

在如上代码中,并没有文件的读写操作,而是直接将文件的数据传输到target方针缓冲区,也便是说,sendfile是无法知道文件的具体的数据的;可是mmap不相同,他是能够修正内核缓冲区的数据的。假定假如需求对文件的内容进行修正之后再传输,只需mmap能够满足。

经过上面的一些介绍,首要便是一个定论,那便是依据零复制技能,能够削减CPU的复制次数和上下文切换次数,然后能够完成文件高效的读写操作。

RocketMQ内部首要是运用依据mmap完成的零复制(其实便是调用上述说到的api),用来读写文件,这也是RocketMQ为什么快的一个很重要原因。

RocketMQ消息短暂而又精彩的一生

RocketMQ中运用mmap代码

CommitLog

前面说到音讯需求耐久化到磁盘文件中,而CommitLog其实便是存储音讯的文件的一个称呼,一切的音讯都存在CommitLog中,一个Broker实例只需一个CommitLog。

因为音讯数据或许会很大,一起兼顾内存映射的功率,不或许将一切音讯都写到同一个文件中,所以CommitLog在物理磁盘文件上被分为多个磁盘文件,每个文件默许的固定大小是1G。

RocketMQ消息短暂而又精彩的一生

当生产者将音讯发送过来的时分,就会将音讯依照次序写到文件中,当文件空间缺乏时,就会从头建一个新的文件,音讯写到新的文件中。

RocketMQ消息短暂而又精彩的一生

音讯在写入到文件时,不仅仅会包含音讯自身的数据,也会包含其它的对音讯进行描绘的数据,比方这个音讯来自哪台机器、音讯是哪个topic的、音讯的长度等等,这些数据会和音讯自身依照必定的次序一起写到文件中,所以图示的音讯其实是包含音讯的描绘信息的。

刷盘机制

RocketMQ在将音讯写到CommitLog文件中时并不是直接就写到文件中,而是先写到PageCache,也便是前面说的内核缓存区,所以RocketMQ供给了两种刷盘机制,来将内核缓存区的数据刷到磁盘。

异步刷盘

异步刷盘便是指Broker将音讯写到PageCache的时分,就直接回来给生产者说音讯存储成功了,然后经过另一个后台线程来将音讯刷到磁盘,这个后台线程是在RokcetMQ发动的时分就会开启。异步刷盘办法也是RocketMQ默许的刷盘办法。

其实RocketMQ的异步刷盘也有两种不同的办法,一种是固守时刻,默许是每隔0.5s就会刷一次盘;另一种便是频率会快点,便是每存一次音讯就会告知去刷盘,但不会去等候刷盘的结果,一起假如0.5s内没被告知去刷盘,也会主动去刷一次盘。默许的是第一种固守时刻的办法。

同步刷盘

同步刷盘便是指Broker将音讯写到PageCache的时分,会等候异步线程将音讯成功刷到磁盘之后再回来给生产者说音讯存储成功。

同步刷盘相对于异步刷盘来说音讯的牢靠性更高,因为异步刷盘或许呈现音讯并没有成功刷到磁盘时,机器就宕机的状况,此刻音讯就丢了;可是同步刷盘需求等候音讯刷到磁盘,那么比较异步刷盘吞吐量会降低。所以同步刷盘合适那种对数据牢靠性要求高的场景。

假如你需求运用同步刷盘机制,只需求在配置文件指定一下刷盘机制即可。

高可用

在说高可用之前,先来完善一下前面的一些概念。

在前面介绍概念的时分也说过,一个RokcetMQ中能够有许多个Broker实例,相同的BrokerName称为一个组,同一个Broker组下每个Broker实例保存的音讯是相同的,不同的Broker组保存的音讯是不相同的。

RocketMQ消息短暂而又精彩的一生

如图所示,两个BrokerA实例组成了一个Broker组,两个BrokerB实例也组成了一个Broker组。

前面说过,每个Broker实例都有一个CommitLog文件来存储音讯的。那么两个BrokerA实例他们CommitLog文件存储的音讯是相同的,两个BrokerB实例他们CommitLog文件存储的音讯也是相同的。

那么BrokerA和BrokerB存的音讯不相同是什么意思呢?

其实很简单了解,假定现在有个topicA存在BrokerA和BrokerB上,那么topicA在BrokerA和BrokerB默许都会有4个行列。

前面在说发音讯的时分需求挑选一个行列进行音讯的发送,假定第一次挑选了BrokerA上的行列发送音讯,那么此刻这条音讯就存在BrokerA上,假定第2次挑选了BrokerB上的行列发送音讯,那么那么此刻这条音讯就存在BrokerB上,所以说BrokerA和BrokerB存的音讯是不相同的。

那么为什么同一个Broker组内的Broker存储的音讯是相同的呢?其实比较简单猜到,便是为了确保Broker的高可用,这样就算Broker组中的某个Broker挂了,这个Broker组依然能够对外供给服务。

那么怎么完成同Broker组的Broker存的音讯数据相同的呢?这就不得不说到Broker的高可用形式。

RocketMQ供给了两种Broker的高可用形式

  • 主从同步形式
  • Dledger形式

主从同步形式

在主从同步形式下,在发动的时分需求在配置文件中指定BrokerId,在同一个Broker组中,BrokerId为0的是主节点(master),其他为从节点(slave)。

当生产者将音讯写入到主节点是,主节点会将音讯内容同步到从节点机器上,这样一旦主节点宕机,从节点机器依然能够供给服务。

主从同步首要同步两部分数据

  • topic等数据
  • 音讯

topic等数据是从节点每隔10s钟主动去主节点拉取,然后更新自身缓存的数据。

音讯是主节点主动推送到从节点的。当主节点收到音讯之后,会将音讯经过两者之间树立的网络连接发送出去,从节点接纳到音讯之后,写到CommitLog即可。

RocketMQ消息短暂而又精彩的一生

从节点有两种办法知道主节点地点服务器的地址,第一种便是在配置文件指定;第二种便是从节点在注册到NameServer的时分会回来主节点的地址。

主从同步形式有一个比较严峻的问题便是假如集群中的主节点挂了,这时需求人为进行干涉,手动进行重启或许切换操作,而非集群自己从从节点中挑选一个节点晋级为主节点。

为了处理上述的问题,所以RocketMQ在4.5.0就引入了Dledger形式。

Dledger形式

在Dledger形式下的集群会依据Raft协议选出一个节点作为leader节点,当leader节点挂了后,会从follower中主动选出一个节点晋级成为leader节点。所以Dledger形式处理了主从形式下无法主动挑选主节点的问题。

在Dledger集群中,leader节点负责写入音讯,当音讯写入leader节点之后,leader会将音讯同步到follower节点,当集群中过半数(节点数/2 +1)节点都成功写入了音讯,这条音讯才算真实写成功。

至于推举的细节,这儿就不多说了,有兴趣的能够自行谷歌,仍是挺有意思的。

音讯消费

总算,在生产者成功发送音讯到Broker,Broker在成功存储音讯之后,顾客要消费音讯了。

顾客在发动的时分会从NameSrever拉取顾客订阅的topic的路由信息,这样就知道订阅的topic有哪些queue,以及queue地点Broker的地址信息。

为什么顾客需求知道topic对应的哪些queue呢?

其实首要是因为顾客在消费音讯的时分是以行列为消费单元的,顾客需求告知Broker拉取的是哪个行列的音讯,至于怎么拉到音讯的,后边再说。

RocketMQ消息短暂而又精彩的一生

消费的两种形式

前面说过,顾客是有个顾客组的概念,在发动顾客的时分会指定该顾客属于哪个顾客组。

DefaultMQPushConsumerconsumer=newDefaultMQPushConsumer("sanyouConsumer");

一个顾客组中能够有多个顾客,不同顾客组之间消费音讯是互不搅扰的。

在同一个顾客组中,音讯消费有两种形式。

  • 集群形式
  • 播送形式
集群形式

同一条音讯只能被同一个消费组下的一个顾客消费,也便是说,同一条音讯在同一个顾客组底下只会被消费一次,这就叫集群消费。

集群消费的完成便是将行列依照必定的算法分配给顾客,默许是依照平均分配的。

RocketMQ消息短暂而又精彩的一生

如图所示,将每个行列分配只分配给同一个顾客组中的一个顾客,这样音讯就只会被一个顾客消费,然后完成了集群消费的效果。

RocketMQ默许是集群消费的形式。

播送形式

播送形式便是同一条音讯能够被同一个顾客组下的一切顾客消费。

其实完成也很简单,便是将一切行列分配给每个顾客,这样每个顾客都能读取topic底下一切的行列的数据,就完成了播送形式。

RocketMQ消息短暂而又精彩的一生

假如你想运用播送形式,只需求在代码中指定即可。

consumer.setMessageModel(MessageModel.BROADCASTING);

ConsumeQueue

上一节咱们说到顾客是从行列中拉取音讯的,可是这儿不经就有一个疑问,那便是音讯明明都存在CommitLog文件中的,那么是怎么去行列中拉的呢?莫非是去遍历一切的文件,找到对应行列的音讯进行消费么?

答案是否定的,因为这种每次都遍历数据的功率会很低,所以为了处理这种问题,引入了ConsumeQueue的这个概念,而消费实际是从ConsumeQueue中拉取数据的。

用户在创立topic的时分,Broker会为topic创立行列,而且每个行列其实会有一个编号queueId,每个行列都会对应一个ConsumeQueue,比方说一个topic在某个Broker上有4个行列,那么就有4个ConsumeQueue。

前面说过,音讯在发送的时分,会依据必定的算法挑选一个行列,之后再发送音讯的时分会携带挑选行列的queueId,这样Broker就知道音讯属于哪个行列的了。当音讯被存到CommitLog之后,其实还会往这条音讯地点的行列的ConsumeQueue插一条数据。

ConsumeQueue也是由多个文件组成,每个文件默许是存30万条数据。

刺进ConsumeQueue中的每条数据由20个字节组成,包含3部分信息,音讯在CommitLog的开始方位(8个字节),音讯在CommitLog存储的长度(8个字节),还有便是tag的hashCode(4个字节)。

RocketMQ消息短暂而又精彩的一生

所以当顾客从Broker拉取音讯的时分,会告知Broker拉取哪个行列(queueId)的音讯、这个行列的哪个方位的音讯(queueOffset)。

queueOffset便是指上图中ConsumeQueue一条数据的编号,单调递加的。

Broker在接受到音讯的时分,找个指定行列的ConsumeQueue,因为每条数据固定是20个字节,所以能够轻易地计算出queueOffset对应的那条数据在哪个文件的哪个方位上,然后读出20个字节,从这20个字节中在解分出音讯在CommitLog的开始方位和存储的长度,之后再到CommitLog中去查找,这样就找到了音讯,然后在进行一些处理操作回来给顾客。

到这,咱们就清楚的知道顾客是怎么从行列中拉取音讯的了,其实便是先从这个行列对应的ConsumeQueue中找到音讯地点CommmitLog中的方位,然后再从CommmitLog中读取音讯的。

RocketMQ怎么完成音讯的次序性

这儿刺进一个比较常见的一个面试,那么怎么确保确保音讯的次序性。

其实要想确保音讯的次序只需确保以下三点即可

  • 生产者将需求确保次序的音讯发送到同一个行列
  • 音讯行列在存储音讯的时分依照次序存储
  • 顾客依照次序消费音讯

RocketMQ消息短暂而又精彩的一生

第一点怎么确保生产者将音讯发送到同一个行列?

上文说到过RocketMQ生产者在发送音讯的时分需求挑选一个行列,而且挑选算法是能够自定义的,这样咱们只需求在依据事务需求,自定义行列挑选算法,将次序音讯都指定到同一个行列,在发送音讯的时分指定该算法,这样就完成了生产者发送音讯的次序性。

第二点,RocketMQ在存音讯的时分,是依照次序保存音讯在ConsumeQueue中的方位的,因为消费音讯的时分是先从ConsumeQueue查找音讯的方位,这样也就确保了音讯存储的次序性。

第三点顾客依照次序消费音讯,这个RocketMQ现已完成了,只需求在消费音讯的时分指定依照次序音讯消费即可,如下面所示,注册音讯的监听器的时分运用MessageListenerOrderly这个接口的完成。

consumer.registerMessageListener(newMessageListenerOrderly(){
@Override
publicConsumeOrderlyStatusconsumeMessage(List<MessageExt>msgs,ConsumeOrderlyContextcontext){
//依照次序消费音讯记录
returnnull;
}
});

音讯整理

因为音讯是存磁盘的,可是磁盘空间是有限的,所以对于磁盘上的音讯是需求整理的。

当呈现以下几种状况下时就会触发音讯整理:

  • 手动履行删去
  • 默许每天凌晨4点会主动整理过期的文件
  • 当磁盘空间占用率默许到达75%之后,会主动整理过期文件
  • 当磁盘空间占用率默许到达85%之后,不管这个文件是否过期,都会被整理掉

上述过期的文件是指文件最终一次修正的时刻超越72小时(默许状况下),当然假如你的老板非常有钱,服务器的磁盘空间非常大,能够将这个过期时刻修正的更长一点。

有的小伙伴肯定会有疑问,假如音讯没有被音讯,那么会被整理么?

答案是会被整理的,因为整理音讯是直接删去CommitLog文件,所以只需到达上面的条件就会直接删去CommitLog文件,不管文件内的音讯是否被消费过。

当音讯被整理完之后,音讯也就完毕了它精彩的终身。

音讯的终身总结

为了更好地了解本文,这儿再来总结一下RokcetMQ音讯终身的各个环节。

音讯发送
  • 生产者产生音讯
  • 生产者在发送音讯之前会拉取topic的路由信息
  • 依据行列挑选算法,从topic众多的行列中挑选一个行列
  • 跟行列地点的Broker机器树立网络连接,将音讯发送到Broker上
音讯存储
  • Broker接纳到生产者的音讯将音讯存到CommitLog中
  • 在CosumeQueue中存储这条音讯在CommitLog中的方位

因为CommitLog和CosumeQueue都涉及到磁盘文件的读写操作,为了进步读写功率,RokcetMQ运用到了零复制技能,其实便是调用了一下Java供给的api。。

高可用

假如是集群形式,那么音讯会被同步到从节点,从节点会将音讯存到自己的CommitLog文件中。这样就算主节点挂了,从节点依然能够对外供给访问。

音讯消费
  • 顾客会拉取订阅的Topic的路由信息,依据集群消费或许播送消费的形式来挑选需求拉取音讯的行列
  • 与行列地点的机器树立连接,向Broker发送拉取音讯的恳求
  • Broker在接纳到恳求知道,找到行列对应的ConsumeQueue,然后计算出拉取音讯的方位,再解分出音讯在CommitLog中的方位
  • 依据解分出的方位,从CommitLog中读出音讯的内容回来给顾客
音讯整理

因为音讯是存在磁盘的,而磁盘的空间是有限的,所以RocketMQ会依据一些条件去整理CommitLog文件。

最终

最终,假如有对RocketMQ源码感兴趣的小伙伴能够从如下地址中拉取RocketMQ源码,里面我现已对RocketMQ一些中心组件的源码进行了注释。

github.com/sanyou3/roc…

往期抢手文章推荐

写出漂亮代码的45个小技巧

项目中引入这玩意,排查日志又快又准!

两万字盘点那些被玩烂了的规划形式

RocketMQ保姆级教程

三万字盘点Spring/Boot的那些常用扩展点

@Async注解的坑,当心