消息存储 CommitLog 1、消息内容和元数据都会存在CommitLog日志文件中 2、消息内容不是定长的 3、单个文件大小默认1G 文件名长度为20位 左边补零 剩余为起始偏移量 比如第一个文件名是 00000000000000000000 起始偏移量为0 文件大小为1G=1073741824 当第一个文件写满了 第二个文件为00000000001073741824 起始偏移量为1073741824 ConsumeQueue(消息消费队列) 主要是为了提高消息消费的性能 消费者通过消息消费队列ConsumeQueue(作为索引) 来查找消费的消息 消费队列ConsumeQueue(索引)内容 ConsumeQueue(逻辑消费队列) 作为消费消息的索引 保存了指定Topic下的队列消息在CommitLog中的 起始物理偏移量offset(8个字节) 消息大小size和(4个字节) 消息Tag的HashCode值(8个字节) 每个条目定长20个字节 单个文件有30W个条目 可以像数组一样随机访问每个条目 每个ConsumeQueue文件大约5.72M consumequeue文件夹的组织方式 topic/queue/file三层组织结构 具体存储路径 $HOME/store/consumequeue/{topic}/{queueId}/{fileName} IndexFile(索引文件) 通过key或时间区间来查询消息 1、存储位置 $HOME storeindex${fileName} 2、文件名 以创建时的时间戳命名的 3、文件大小 单个文件约为400M 可以存储2000W个索引 存储结构 Broker是混合型存储结构 单个broker所有队列共用一个日志文件 索引和数据分离的存储结构 生产者和消费者使用索引和数据相分离的存储结构 整体流程 1、Producer发送消息至Broker端 2、Broker端使用同步或者异步的方式对消息刷盘持久化 保存至CommitLog中 3、Consumer拉取消息 4、服务端也支持长轮询模式 Broker允许等待30s的时间 只要这段时间内有新消息到达 将直接返回给消费端 页缓存和内存映射 页缓存(PageCache)是OS对文件的缓存 用于加速对文件的读写 程序对文件进行顺序读写的速度几乎接近于内存的读写速度 主要原因就是由于OS使用PageCache机制对读写访问操作进行了性能优化 将一部分的内存用作PageCache 对于数据的写入 OS会先写入至Cache内 随后通过异步的方式由pdflush内核线程将Cache内的数据刷盘至物理磁盘上 对于数据的读取 如果一次读取文件时出现未命中PageCache的情况 OS从物理磁盘上访问读取文件的同时 会顺序对其他相邻块的数据文件进行预读取 消费队列的读性能几乎接近读内存的原因 在消息堆积的情况下也不会影响性能 存储的数据较少 page cache机制预读取 顺序读取 消息存储的日志数据文件CommitLog的读取会严重影响性能 读取消息内容会产生较多的随机访问读取 如何提升随机读性能 在块存储采用SSD的话 选择合适的系统IO调度算法 比如Deadline算法 随机读的性能会有所提升 文件的读写操作传统的IO方式 传统的IO方式 用户态空间的进程要读写磁盘文件 需要经过内核空间 用户进程访问内核空间的缓存 如果没有的话 则读取磁盘文件 用户进程写入文件 先写入内核空间的socket缓存 再通过网卡写入到磁盘 经过2次DMA拷贝+2次CPU拷贝 4次上下文切换 RocketMQ mmap+write 1、页缓存是对磁盘数据的缓存 2、用户先读取页缓存 如果没有数据则读取磁盘 根据局部性原理 将相邻磁盘块读入页缓存 3、用户将数据写入页缓存 异步线程将小的写入操作合并成大的写入 然后刷入磁盘 4、顺序写入磁盘 磁头几乎不用换道 5、异步刷盘 容易丢失数据 a、可以同步刷盘 但性能低 b、一般采用多副本机制保证消息的可靠 6、数据追加到日志文件的尾部 老的消息无法更改 mmap利用内存映射文件来避免拷贝 用户空间可以通过映射地址加偏移量的方式直接操作内核空间的页缓存 避免了内核态再拷贝到用户态 rocketmq采用mmap+write方式实现文件读写操作 产生2次DMA拷贝+1次CPU拷贝 4次上下文切换 通过内存映射减少了一次CPU拷贝 可以减少内存使用 适合大文件的传输 对比Kafafa sendfile sendfile 1、用户进程通过sendfile()方法向操作系统发起调用上下文从用户态转向内核态 2、DMA控制器把数据从硬盘中拷贝到读缓冲区 3、CPU将读缓冲区中数据拷贝到socket缓冲区 DMA控制器把数据从socket缓冲区拷贝到网卡 上下文从内核态切换回用户态 sendfile调用返回 2次用户态和内核态的上下文切换 3次拷贝 sendfile方法IO数据对用户空间完全不可见 所以只能适用于完全不需要用户空间处理的情况 比如静态文件服务器 sendfile+DMA gather 1、用户进程通过sendfile()方法向操作系统发起调用 上下文从用户态转向内核态 2、DMA控制器利用scatter把数据从硬盘中拷贝到读缓冲区离散存储 3、CPU把读缓冲区中的文件描述符和数据长度发送到socket缓冲区 4、DMA控制器根据文件描述符和数据长度 使用scatter/gather把数据从内核缓冲区拷贝到网卡 5、sendfile()调用返回,上下文从内核态切换回用户态 DMA gather和sendfile一样数据对用户空间不可见 而且需要硬件支持 同时输入文件描述符只能是文件 但是过程中完全没有CPU拷贝过程 极大提升了性能 产生2次DMA拷贝 没有CPU拷贝 而且也只有2次上下文切换