消息存储
存储方式
RocketMQ 使用文件系统持久化消息。性能要比使用DB产品要高。文件顺序读写的速度大概是3G左右。
RocketMQ在消息存储时使用了数据零拷贝技术来发送数据文件数据。
MappedByteBuffer 内存映射
- MappedByteBuffer使用虚拟内存,因此分配(map)的内存大小不受JVM的-Xmx参数限制(map方法中size的类型是long,long的最大值换算得MappedByteBuffer的最大值是2G)
- 当文件超出1.5G限制时,可以通过position参数重新map文件后面的内容
- MappedByteBuffer在处理大文件时的确性能很高,但也存在一些问题,如内存占用、文件关闭不确定,被其打开的文件只有在垃圾回收的才会被关闭,且这个时间点不确定
为了使用零拷贝技术,RocketMQ的文件存储大小默认每个1G,超过1G会重新建立一个新文件
存储结构
CommitLog:
存储消息的详细内容,按照消息收到的顺序,所有消息都存储在一起。每个消息存储后都会有一个offset,代表在commitLog中的偏移量。
默认配置是MessageStoreConfig
核心方法为 putMessage 写入消息
CommitLog内部结构
MappedFileQueue -> MappedFile MappedFile默认大小为1G
ConsumerQueue
通过消息偏移量建立的消息索引。
针对每个Topic创建,消费逻辑队列,存储位置信息,用来快速定位CommitLog中的数据位置。
启动后会被加载到内存中,加快查找消息速度。
以Topic作为文件名称,每个Topic下又以queue id作为文件夹分组。
默认大小为48M
indexFile
消息的Key和时间戳索引,默认存储路径为/root/store/
刷盘机制
同步刷盘:
消息被Broker写入磁盘后再给producer响应
异步刷盘:
消息被Broker写入内存后立即给producer响应,当内存中消息堆积到一定程度的时候写入磁盘持久化
每间隔10ms,执行一次数据持久化操作。
写入消息时,避免消息分割到两个MapedFile中做了以下处理:
// Determines whether there is sufficient free space
if ((msgLen + END_FILE_MIN_BLANK_LENGTH) > maxBlank) {
this.resetByteBuffer(this.msgStoreItemMemory, maxBlank);
// 1 TOTALSIZE
this.msgStoreItemMemory.putInt(maxBlank);
// 2 MAGICCODE
不够放下一个消息的时候,用魔术字符代替
this.msgStoreItemMemory.putInt(CommitLog.BLANK_MAGIC_CODE);
// 3 The remaining space may be any value
// Here the length of the specially set maxBlank
final long beginTimeMills = CommitLog.this.defaultMessageStore.now();
byteBuffer.put(this.msgStoreItemMemory.array(), 0, maxBlank);
return new AppendMessageResult(AppendMessageStatus.END_OF_FILE, wroteOffset, maxBlank, msgId, msgInner.getStoreTimestamp(),
queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills);
}
消息堆积
消息被消费后不会立即删除,每条消息都会持久化到CommitLog中,每个consumer连接到broker后会维持消费进度信息,当有消息消费后只是当前consumer的消费进度(CommitLog的offset)更新了。
清理过期消息:默认48小时后会删除不再使用的CommitLog文件,删除时先检查这个文件最后访问时间,再判断是否大于过期时间,然后在指定时间删除(默认是凌晨4点)。
NameServer特性
功能
NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。
- Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
- 路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费
特性
- NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息。
- NameServer实例时间互不通信,这本身也是其设计亮点之一,即允许不同NameServer之间数据不同步(像Zookeeper那样保证各节点数据强一致性会带来额外的性能消耗)