RocketMQ之消息存储篇(一):Java也能做高性能存储?

一、RocketMQ存储概要设计

RMQ主要存储的文件包括commitlog文件、consumeQueue文件、IndexFile文件。

CommitLog是消息存储文件,所有消息主题的消息都存储在CommitLog文件中;ConsumeQueue是消息消费队列文件,消息达到commitlog文件后将被异步转发到消息消费队列,供消息消费者消费;IndexFile是消息索引文件,主要存储的是key和offset的对应关系。
RocketMQ的数据流向如下图所示:
RocketMQ之消息存储篇(一):Java也能做高性能存储?_第1张图片

二、消息发送存储流程

2.1、消息存储实现类:DefaultMessageStore

DefaultMessageStore是消息存储中最重要的一个类,包含了大量对存储文件操作的API,可以说其他模块要想对消息进行操作都必须经过DefaultMessageStore类。

    private final MessageStoreConfig messageStoreConfig;	//消息存储配置属性
    private final CommitLog commitLog;                      //commitLog文件的存储实现类
    //消息队列存储缓存表,按消息主题分组
    private final ConcurrentMap> consumeQueueTable;
    private final FlushConsumeQueueService flushConsumeQueueService;//:消息队列文件 ConsumeQueue刷盘线程
    private final CleanCommitLogService cleanCommitLogService;      //清除CommitLog文件服务
    private final CleanConsumeQueueService cleanConsumeQueueService;//清除ConsumeQueue文件服务
    private final IndexService indexService;                        //索引文件实现类
    private final AllocateMappedFileService allocateMappedFileService;//Mapped分配服务
    private final ReputMessageService reputMessageService;          //CommitLog消息分发
    private final HAService haService;                              //存储HA机制
    private final TransientStorePool transientStorePool;            //消息堆内存缓存
    private final RunningFlags runningFlags = new RunningFlags();
    private final SystemClock systemClock = new SystemClock();
    private final ScheduledExecutorService scheduledExecutorService =
        Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreScheduledThread"));
    private final BrokerStatsManager brokerStatsManager;
    //消息拉取长轮询模式消息达到监听器
    private final MessageArrivingListener messageArrivingListener;  
    private final BrokerConfig brokerConfig;                        //Broker配置属性
    //文件刷盘监测点
    private StoreCheckpoint storeCheckpoint;
    private AtomicLong printTimes = new AtomicLong(0);
    //CommitLog文件转发请求
    private final LinkedList dispatcherList;

2.2、消息发送存储过程

消息发送存储的追踪可以DefaultMessageStore的putMessage方法开始,一步步Debug之后,我们用一张图来描述消息发送存储的流程:
RocketMQ之消息存储篇(一):Java也能做高性能存储?_第2张图片

三、存储文件组织与内存映射机制

RocketMQ通过使用内存映射文件来提高IO的访问性能,无论是CommitLog、ConsumeQueue还是IndexFile,单个文件都被设计为固定长度,如果一个文件写满以后就再创建一个新文件,文件名就为该文件第一条消息对应的全局物理偏移量。

3.1、MappedFile

MappedFile是封装存储文件的关键类,也是内存映射文件的具体实现,核心属性如下:

public static final int OS_PAGE_SIZE = 1024 * 4;    //操作系统每页大小,默认4k
//当前JVM实例中MappedFile虚拟内存
private static final AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0);
//当前JVM实例中MappedFile对象个数
private static final AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0);
//当前文件的写指针,从0开始
protected final AtomicInteger wrotePosition = new AtomicInteger(0);
//当前文件的提交指针
protected final AtomicInteger committedPosition = new AtomicInteger(0);
//刷写到磁盘指针,该指针之前的数据持久化到磁盘中
private final AtomicInteger flushedPosition = new AtomicInteger(0);
protected int fileSize;                             //文件大小
protected FileChannel fileChannel;                  //文件通道
protected ByteBuffer writeBuffer = null;            //堆内存ByteBuffer
//堆内存池
protected TransientStorePool transientStorePool = null;
private String fileName;                            //文件名称
private long fileFromOffset;                        //文件初始偏移量
private File file;                                  //物理文件
private MappedByteBuffer mappedByteBuffer;          //物理文件对应的内存映射
private volatile long storeTimestamp = 0;           //文件最后一次内容写入时间
//是否是MappedFileQueue队列中第一个文件
private boolean firstCreateInQueue = false;         

MappedFile写入流程调用的方法链如下图所示:
RocketMQ之消息存储篇(一):Java也能做高性能存储?_第3张图片

3.2、MappedFileQueue

MappedFileQueuede是MappedFile的管理容器,使用CopyOnWriteArrayList管理MappedFile,封装了commitLog的存储目录

    private final String storePath;				//存储目录
    private final int mappedFileSize;			//单个文件的存储大小
    //MappedFile文件集合
    private final CopyOnWriteArrayList mappedFiles = new CopyOnWriteArrayList();
    //创建MappedFile服务类
    private final AllocateMappedFileService allocateMappedFileService;
    private long flushedWhere = 0;				//当前刷盘指针,表示该指针之前的所有数据持久化到磁盘
    private long committedWhere = 0;			//当前数据提交指针,内存中ByteBuffer的写指针,该值大于等于flushedWhere

3.3、TransientStorePool

临时存储池。RocketMQ单独创建一个MappedByteMapper内存缓存池,用来临时存储数据,数据先写入该内存映射中,然后由commit线程定时将数据从该内存复制到与目的物理文件对应的内存映射中。

private final int poolSize;                         //可用Buffer的个数
private final int fileSize;                         //每个By特Buffer的大小
private final Deque availableBuffers;   //Buffer容器,双端队列
private final MessageStoreConfig storeConfig;       //配置

四、RocketMQ存储文件

4.1、CommitLog文件

4.1.1、物理组织方式

RocketMQ的存储文件(CommitLog、ConsumeQueue、IndexFile)都被设计为固定长度,如果一个文件写满以后再创建一个新文件,文件名就为该文件第一条消息对应的全局物理偏移量。

4.1.2、消息存储格式

  • 1 ) TOTALSIZE:该消息条目总长度,4字节
  • 2 ) MAGICCODE:魔数,4字节,固定值0xdaa320a7
  • 3 ) BODYCRC:消息体crc校验码,4字节
  • 4 ) QUEUEID:消息消费队列ID,4字节
  • 5 ) FLAG:消息FLAG,RocketMQ不做处理,供应用程序使用,默认4字节
  • 6 ) QUEUEOFFSET :消息在消息消费队列的偏移量,8字节
  • 7 ) PHYSICALOFFSET:消息在 CommitLog文件中的偏移量,8字节
  • 8 ) SYSFLAG:消息系统 Flag ,例如是否压缩、是否是事务消息等,4字节
  • 9 ) BORNTIMESTAMP:消息生产者调用消息发送API的时间戳, 8字节
  • 10 ) BORNHOST:消息发送者IP、端口号,8字节
  • 11 ) STORETIMESTAMP: 消息存储时间戳,8字节
  • 12 ) STOREHOSTADDRESS: Broker 服务器 IP+端口号,8字节
  • 13 ) RECONSUMETIMES:消息重试次数,4字节
  • 14 ) Prepared Transaction Offset:事务消息物理偏移量,8字节
  • 15 ) BodyLength :消息体长度,4字节
  • 16 ) Body:消息体内容,长度为 bodyLength 中存储的值
  • 17 ) TopieLength:主题存储长度,1字节,表示主题名称最多 255 字符
  • 18 ) Topic: 主题,长度为 TopieLength 中存储的值
  • 19 ) PropertiesLength:消息属性长度,2字节,表示消息属性长度不能超过 65536个字符
  • 20 ) Properties:消息属性,长度为 PropertiesLength 中存储的值

4.1.3、消息的查找实现

1)、首先获取当前commitlog目录的最小偏移量:首先获取目录下的第一个文件,如果该文件可用,则返回该文件的起始偏移量,否则返回下一个文件的起始偏移量;
2)、根据该offset返回下一个文件的起始偏移量:后获取一个文件的大小,减去(offset%MappedFileSize)其目的是回到下一文件的起始偏移量;
3)、根据偏移量与消息长度查找消息:首先根据偏移找到所在的物理偏移量,然后用offset与文件长度取余得到在文件内的偏移量,从该偏移量读取size长度的内容返回即可。如果只根据消息便宜查找消息,则首先找到文件内的偏移量,然后尝试读取4个字节获取消息的实际长度,最后读取指定字节即可。

4.2、ConsumeQueue文件

ComsumeQueue可以看成是CommitLog关于消息消费的“索引”文件,ConsumeQueue的第一级目录为消息主题,第二级目录为主题的消息队列:
为了加速ConsumeQueue消息条目的检索速度与节省磁盘空间,ConsumeQueue中的存储单元是一个20 字节定长的数据,包含8字节的commitlogoffset(这条消息在commitlog文件的实际偏移量)、4字节的消息大小、以及消息的hash值
RocketMQ之消息存储篇(一):Java也能做高性能存储?_第4张图片

4.3、Index索引文件

为了提高根据主题与消息队列检索消息的速度,RocketMQ引入了hash所以机制为消息建立索引,这里借用了HashMap的思想:
RocketMQ之消息存储篇(一):Java也能做高性能存储?_第5张图片
1)、IndexHeader头部,包含40个字节,记录该IndexFile的统计信息,其结构如下:

  • beginTimestamp:包含消息的最小存储时间
  • endTimestamp: 包含消息的最大存储时间
  • beginPhyoffset:包含消息的最小物理偏移量(commitlog文件偏移量)
  • endPhyoffset:包含消息的最大物理偏移量(commitlog文件偏移量)
  • hashslotCount:hashslot个数,使用意义不大
  • indexCount:Index条目列表当前已使用的个数

2)、Hash槽,一个IndexFile默认包含500万个Hash槽,每个hash槽存储的是落在该hash槽的hashcode最新的Index的索引

3)、Index条目列表,默认一个索引文件包含2000万个条目,每一个Index条目结构如下:

  • hashCode:key的hashcode
  • phyoffset:消息对应的物理偏移量
  • timedif:该消息村粗时间与第一条消息的时间戳的差值,小于0该消息无效
  • preIndexNo:该条目的前一条记录的Index索引,当出现hash冲突时,构建的链表结构

4.4、CheckPoint文件

checkpoint的作业是记录commitlog、ConsumeQueue、index文件的刷盘时间点,文件固定长度为4k,其中只用该文件的前面24个字节
RocketMQ之消息存储篇(一):Java也能做高性能存储?_第6张图片

  • physicMsgTimeStamp:commitlog文件刷盘时间点
  • logicsMsgTimestamp:consumeQueue文件刷盘时间点
  • indexMsgTimestamp:索引文件刷盘时间点

你可能感兴趣的:(分布式,RocketMQ)