这里根据RocketMQ的前期操作将整理Broker的存储设计及核心层次关系,先上图,根据源码的分析及整理,最终的核心层级关系如下:
1,通信层采用netty来进行网络的接受及处理
2,核心的处理有针对producer的发送服务事件SendMessageProcessor,关于consumer的拉取服务事件PullMessageProcessor
3,无论是哪种基于数据的处理,底层都是基于数据存储的封装对象MessageStore的实现DefaultMessageStore
4,DefaultMessageStore包装了核心的数据结构,罗列如下
4.1 consumeQueueTable封装了topic对应的queueId及关联的ConsumeQueue,核心是ConsumeQueue的对象,记录着consumer的消费信息
4.2 CommitLog对象是接受数据的实际操作对象封装,大部分基于数据的操作都是基于该对象
4.3 IndexService索引服务,特殊功能,如果producer发送时增加properties中增加了keys的操作,会根据设定生成对应的索引,便于基于key的查询
5,分两种情况处理,主要是基于核心业务的操作和基于扩展个性的操作
5.1 MappedFileQueue对象,内部将所有的存在且可用的文件进行排序保存成有序的队列,便于查找和定位
5.2 indexFileList本身是集合,核心是IndexFile,集合的意思和上面的队列功能相似,核心实现是IndexFile操作
6,文件映射内存的封装,MappedFile的高性能就体现在这里,提供了顺序写,随机读,内存数据刷盘,清理无关数据,可从该类的属性解读
public class MappedFile extends ReferenceResource { public static final int OS_PAGE_SIZE = 1024 * 4; protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0); private static final AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0); //当前文件写的位置 protected final AtomicInteger wrotePosition = new AtomicInteger(0); //ADD BY ChenYang //当前文件被commit的位置 protected final AtomicInteger committedPosition = new AtomicInteger(0); //即将数据刷到盘的位置 private final AtomicInteger flushedPosition = new AtomicInteger(0); //文件的大小 protected int fileSize; //顶层转换通道 protected FileChannel fileChannel; /** * Message will put to here first, and then reput to FileChannel if writeBuffer is not null. */ protected ByteBuffer writeBuffer = null; //内存池,根据配置 protected TransientStorePool transientStorePool = null; //文件名 private String fileName; //文件映射的开始位置 private long fileFromOffset; //文件 private File file; //内存映射 private MappedByteBuffer mappedByteBuffer; //存储数据的时间 private volatile long storeTimestamp = 0; //是否是第一次创建 private boolean firstCreateInQueue = false;
7,根据调用顺序和核心功能解析
7.1 FileChannel对象,根据jdk中的io转换为nio的channel,便于基于nio的操作,核心转换操作
7.2 MappedByteBuffer,核心高性能的体现,将整个1G文件的内容映射到当前服务器的虚拟内存中,进行高效io操作
7.3 ByteBuffer,nio的核心对象,基于缓存的操作,这里说下最大的区别:io是基于单个字节的数据操作,nio是基于数据块的操作
8,这里就是底层服务器的io操作
特别声明:高效的另一个操作就是顺序写和随机读
顺序写:根据源码查看分两种情况,一种是根据用户的请求,内部根据请求计算位置,同时根据内部协议进行序列化后保存数据;第二种是直接提供二进制接口,外部根据协议进行序列化直接调用保存接口
//将消息放置到缓冲区中,接受的回调方法 public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb) { assert messageExt != null; assert cb != null; //获取当前的文件写的偏移位置 int currentPos = this.wrotePosition.get(); if (currentPos < this.fileSize) { ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice(); //当前的位置 byteBuffer.position(currentPos); AppendMessageResult result = null; //区分单次和批量提交的操作,核心操作在回调接口 if (messageExt instanceof MessageExtBrokerInner) { result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt); } else if (messageExt instanceof MessageExtBatch) { result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBatch) messageExt); } else { return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } this.wrotePosition.addAndGet(result.getWroteBytes()); this.storeTimestamp = result.getStoreTimestamp(); return result; } log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize); return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } public long getFileFromOffset() { return this.fileFromOffset; } //外部基于保存的序列化传递过来二进制 public boolean appendMessage(final byte[] data) { int currentPos = this.wrotePosition.get(); if ((currentPos + data.length) <= this.fileSize) { try { this.fileChannel.position(currentPos); this.fileChannel.write(ByteBuffer.wrap(data)); } catch (Throwable e) { log.error("Error occurred when append message to mappedFile.", e); } this.wrotePosition.addAndGet(data.length); return true; } return false; }
随机读:随机读的操作在该类中同样分两种情况,一种是从指定位置开始读指定大小的数据,第二种是从指定位置开始读取所有数据
public SelectMappedBufferResult selectMappedBuffer(int pos, int size) { //获得读取内容的位置 int readPosition = getReadPosition(); //读取数据的验证 if ((pos + size) <= readPosition) { //安全的验证 if (this.hold()) { //将原缓冲区中的共享出一个片段,底层数据一致 ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); //构造位置 byteBuffer.position(pos); ByteBuffer byteBufferNew = byteBuffer.slice(); //构造容量 byteBufferNew.limit(size); return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this); } else { log.warn("matched, but hold failed, request pos: " + pos + ", fileFromOffset: " + this.fileFromOffset); } } else { log.warn("selectMappedBuffer request pos invalid, request pos: " + pos + ", size: " + size + ", fileFromOffset: " + this.fileFromOffset); } return null; } public SelectMappedBufferResult selectMappedBuffer(int pos) { //获得读取的位置 int readPosition = getReadPosition(); //验证长度 if (pos < readPosition && pos >= 0) { //安全的验证 if (this.hold()) { //将原缓冲区中的共享出一个片段,底层数据一致 ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(pos); //计算读取数据的大小 int size = readPosition - pos; ByteBuffer byteBufferNew = byteBuffer.slice(); byteBufferNew.limit(size); return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this); } } return null; }