RocketMQ的数据存储部分也是一个重头戏,他主要用于存储Producer生产的消息,Consume的逻辑队列,索引,以及主从复制,这里也是一个非常好的范例,我们可以看到如何处理数据存储,如何提高IO效率。
东西太多信息量略大有点乱,之后再整理吧......
一、总体结构
CommitLog:实时执行,真正的I/O写入磁盘操作, 要求是实时的(当然,有时候只是写入内存,定时刷盘);
ConsumeQueue:后台非实时执行,根据CommitLog,生成ConsumeQueue的信息,其记录了每个queue的物理commitOffset和逻辑logicOffset的信息;
IndexService:后台非实时执行,如果发送消息的propety字段里面有keys字段,那么会将他以空格为分隔符,生成key和对应的index信息;
HAService:后台非实时执行,处理和slave之间的信息备份。
队列逻辑结构:
逻辑上来看,每一个Topic有很多queue,他们各自处于自己的ConsumeQueue进行处理二、基础数据结构
MapedFileQueue:包含了很多MapedFile,以及每个MapedFile的真实大小;
MapedFile:包含了具体的文件信息,包括文件路径,文件名,文件起始偏移,写位移,读位移等等信息,同时使用了虚拟内存映射来提高IO效率;
这两个数据结构是真实的保存了存放在物理机器上的文件信息,后续的很多模块如果涉及到文件存储,都会使用到这两个数据结构。
2.1. MapedFileQueue:
// 每次触发删除文件,最多删除多少个文件
private static final int DeleteFilesBatchMax = 10;
// 文件存储位置
private final String storePath;
// 每个文件的大小
private final int mapedFileSize;
// 各个文件
private final List<MapedFile> mapedFiles = new ArrayList<MapedFile>();
// 读写锁(针对mapedFiles)
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 预分配MapedFile对象服务
private final AllocateMapedFileService allocateMapedFileService;
// 刷盘刷到哪里
private long committedWhere = 0;
// 最后一条消息存储时间
private volatile long storeTimestamp = 0;
2.2. MapedFile:保存了文件的详细信息,包括:
TotalMapedVitualMemory:JVM中映射的虚拟内存总大小
TotalMapedFiles:JVM中mmap的数量
fileName:文件名
fileFromOffset:文件的起始偏移量
fileSize:文件大小
file:文件句柄
mappedByteBuffer:映射的内存对象
wrotePostion:当前文件的写位置
committedPosition:当前文件Flush到的位置
fileChannel:映射的FileChannel对象
storeTimestamp:最后一条消息保存时间
firstCreateInQueue:是不是刚刚创建的Map
2.3. AllocateMappedFileService:用于异步创建MappedFile,其实这里和通讯的I/O操作是一样的,也是采用异步创建文件的方式,超时会报错,其中的细节与通讯的ResponseFuture类似,通过AllocateRequest里面的countdown来判断是否创建成功。
2.4. 写入文件的优化策略:
由于每次都写入磁盘,其实是非常慢的,因此对于写盘操作有2种优化方式:
第一种方式是写入filechannel map的缓存:mappedByte