2026/4/18 3:09:04
网站建设
项目流程
网站建设方案主要是,wordpress图片素材主题,公司网站域名备案流程,仿70网站分类目录源码引言
消息队列的存储架构是决定其可靠性、吞吐量、延迟性能的核心因素#xff0c;直接影响业务场景适配能力。本文聚焦三款主流消息队列 ——Kafka#xff08;LinkedIn 开源#xff0c;侧重高吞吐#xff09;、RocketMQ#xff08;阿里开源#xff0c;金融级特性突出直接影响业务场景适配能力。本文聚焦三款主流消息队列 ——KafkaLinkedIn 开源侧重高吞吐、RocketMQ阿里开源金融级特性突出、JMQ京东开源侧重高可用与灵活性从存储模型、数据组织、索引设计等维度展开深度对比为技术选型与架构优化提供参考。本文将从概念辨析出发系统拆解主流存储模型与存储引擎的设计逻辑对比 JMQ、Kafka、RocketMQ的技术选型差异与架构设计。一、Kafka存储架构1.1 核心存储模型分区日志流Topic - 主题Kafka学习了数据库里面的设计在里面设计了topic主题这个东西类似于关系型数据库的表此时我需要获取中国移动的数据那就直接监听中国移动订阅的Topic即可。Partition - 分区Kafka还有一个概念叫Partition分区分区具体在服务器上面表现起初就是一个目录一个主题下面有多个分区这些分区会存储到不同的服务器上面或者说其实就是在不同的主机上建了不同的目录。这些分区主要的信息就存在了.log文件里面。跟数据库里面的分区差不多是为了提高性能。至于为什么提高了性能很简单多个分区多个线程多个线程并行处理肯定会比单线程好得多。Topic和partition像是HBASE里的table和region的概念table只是一个逻辑上的概念真正存储数据的是region这些region会分布式地存储在各个服务器上面对应于kafka也是一样Topic也是逻辑概念而partition就是分布式存储单元。这个设计是保证了海量数据处理的基础。我们可以对比一下如果HDFS没有block的设计一个100T的文件也只能单独放在一个服务器上面那就直接占满整个服务器了引入block后大文件可以分散存储在不同的服务器上。注意1.分区会有单点故障问题所以我们会为每个分区设置副本数2.分区的编号是从0开始的Kafka 以「主题Topic- 分区Partition」为核心组织数据每个分区本质是一个 append-only 的日志流消息按生产顺序追加存储保证分区内消息有序性。优点可以充分利用磁盘顺序读写高性能的特性。存储介质也可以选择廉价的SATA磁盘这样可以获得更长的数据保留时间、更低的数据存储成本。1.2 数据组织分段日志文件•每个分区拆分为多个 Segment 文件默认 1GB命名格式为「起始偏移量.log」如 00000000000000000000.log做这个限制目的是为了方便把.log加载到内存去操作•配套两类索引文件.index偏移量→物理地址映射、.timeindex时间戳→偏移量映射这个9936472之类的数字就是代表了这个日志段文件里包含的起始offset也就说明这个分区里至少都写入了接近1000万条数据了。Kafka broker有一个参数log.segment.bytes限定了每个日志段文件的大小最大就是1GB一个日志段文件满了就自动开一个新的日志段文件来写入避免单个文件过大影响文件的读写性能这个过程叫做log rolling正在被写入的那个日志段文件叫做active log segment。1.3 消息读/写过程写消息•Index文件写入Index文件较小可以直接用mmap进行内存映射避免频繁的磁盘I/O操作提高写入性能由于Index文件是稀疏索引只需要记录关键位置的偏移量因此即使使用mmap写入的开销也相对较低。•Segment文件写入Segment文件较大可以采用普通的写操作FileChannel.write由于Segment文件是顺序写入的并且Kafka会利用操作系统的PageCache页缓存机制写入操作会先写入到内存中然后由操作系统在后台异步刷新到磁盘可以进一步提高写入的性能。读消息•Index文件读取通常使用mmap方式读取由于Index文件较小且是稀疏索引缺页中断的可能性较小。•Segment文件读取通常使用sendfile系统调用来实现零拷贝读取和发送减少数据在用户空间与内核空间之间的拷贝次数提高数据传输的效率。1.4 关键技术Kafka 作为高性能的消息中间件其超高吞吐量的核心秘诀之一就是深度依赖 PageCache 顺序 I/O mmap 内存映射的组合。PageCache中文名称为页高速缓冲存储器。它是将磁盘上的数据加载到内存中当系统需要访问这些数据时可以直接从内存中读取而不必每次都去读取磁盘。这种方式显著减少了磁盘I/O操作从而提高了系统性能。mmapMemory-mapped file是操作系统提供的一种将磁盘文件与进程虚拟地址空间建立映射关系的核心技术本质是让进程通过直接操作内存地址的方式读写文件无需传统的 read/write 系统调用。核心价值在于零拷贝和内存式文件访问尤其适合大文件、高吞吐、随机访问的场景。将日志段.log文件映射到内存生产者写入时直接写内存内核异步刷盘消费者读取时直接从内存读取实现超高吞吐Kafka 的 “顺序写 mmap” 是其高性能核心零拷贝流程示意图零拷贝过程1.用户进程发起sendfile系统调用上下文切换1从用户态转向内核态2.DMA控制器把数据从硬盘中拷贝到内核缓冲区。3.CPU将读缓冲区中数据拷贝到socket缓冲区4.DMA控制器异步把数据从socket缓冲区拷贝到网卡5.上下文切换2从内核态切换回用户态sendfile调用返回。1.5 设计优势•顺序写磁盘Segment 文件仅追加写入规避随机 IO吞吐量极高单分区可达 10 万 TPS•索引轻量化仅维护偏移量与时间戳索引降低存储开销•副本同步基于 ISR 机制仅同步已提交消息兼顾一致性与可用性二、RocketMQ存储架构Kafka的每个Partition都是一个完整的、顺序写入的文件但当Partition数量增多时从操作系统的角度看这些写入操作会变得相对随机这可能会影响写入性能。2.1 核心存储模型分离式设计RocketMQ采用「CommitLog ConsumeQueue IndexFile」三层结构彻底分离数据存储与索引查询•CommitLog全局单一日志文件默认 1GB / 个循环覆盖存储所有主题的原始消息•ConsumeQueue按主题 - 队列维度拆分的索引文件存储「消息物理地址 偏移量 长度」供消费者快速查询•IndexFile哈希索引文件支持按消息 Key 查询CommitLog消息的原始日记本CommitLog是RocketMQ存储消息的物理文件所有消息都会按到达顺序写入这个文件。你可以把它想象成一本不断追加的日记本——每条消息都是按时间顺序记录的新日记。// 消息存储的核心逻辑简化示例非源码public void putMessage(Message message) {// 1. 将消息序列化为字节数组byte[] data serialize(message);// 2. 计算消息物理偏移量long offset commitLog.getMaxOffset();// 3. 将数据追加到CommitLog文件末尾commitLog.append(data);// 4. 返回消息的全局唯一物理偏移量return offset;}消息写入CommitLog时有三个关键特性1.顺序写入所有消息按到达顺序追加到文件末尾避免磁盘随机寻址2.内存映射通过MappedByteBuffer实现文件映射减少数据拷贝次数3.文件分割单个CommitLog文件默认1GB写满后创建新文件文件名用起始偏移量命名举个例子当生产者发送三条消息时CommitLog文件可能长这样0000000000000000000文件11GB2|–消息A(offset0)3|–消息B(offset100)4|–消息C(offset200)500000000001073741824文件2起始偏移量1073741824温馨提示虽然CommitLog是顺序写但读取时需要配合索引结构否则遍历文件找消息就像大海捞针。消费队列ConsumeQueue消息的快速目录如果每次消费都要扫描CommitLog性能会惨不忍睹。于是RocketMQ设计了ConsumeQueue——它是基于Topic和Queue的二级索引文件。每个ConsumeQueue条目包含三个关键信息固定20字节1| CommitLog Offset (8字节) | Message Size (4字节) | Tag Hashcode (8字节) |这相当于给CommitLog里的消息做了一个目录TopicA-Queue0的ConsumeQueue2|–0对应CommitLog偏移0的消息A3|–100对应CommitLog偏移100的消息B4|–200对应CommitLog偏移200的消息C当消费者拉取TopicA-Queue0的消息时1.先查ConsumeQueue获取消息的物理位置2.根据CommitLog Offset直接定位到CommitLog文件3.读取指定位置的消息内容关键设计点•ConsumeQueue采用内存映射异步刷盘保证高性能•单个文件存储30万条索引约5.72MB30万*20字节•通过hashCode快速过滤Tag实现消息过滤索引文件IndexFile消息的全局字典如果需要根据MessageID或Key查询消息ConsumeQueue就不够用了。这时候就要用到IndexFile这个全局索引。IndexFile的结构类似HashMap1.Slot槽位500万个存储相同hash值的Index条目链表头2.Index条目2000万条包含Key的hash值、CommitLog偏移量、时间差等信息当写入消息时// 索引构建过程简化示意public void buildIndex(Message message) {// 计算Key的hash值int hash hash(message.getKey());// 定位到对应的Slot槽位int slotPos hash % slotNum;// 在Index区域追加新条目indexFile.addEntry(hash, message.getCommitLogOffset());}查询时通过两次查找快速定位1.根据Key的hash值找到Slot槽位2.遍历Slot对应的链表比对CommitLog中的实际Key值性能优化必知•消息体积差异大时CommitLog仍然保持顺序写但ConsumeQueue可能出现「稀疏索引」相邻索引指向的物理位置间隔大•生产环境中CommitLog建议放在单独SSD磁盘ConsumeQueue和IndexFile可放普通磁盘•遇到消息堆积时优先检查消费者速度而不是无脑扩容Broker存储理解这些底层机制下次遇到消息查询性能问题或者磁盘IO瓶颈时就知道该从CommitLog的写入模式还是ConsumeQueue的索引结构入手排查了。2.2 数据流转机制•生产者写入 CommitLog生成全局唯一偏移量PHYOFFSET•后台线程异步构建 ConsumeQueue 索引同步消息元数据•消费者通过 ConsumeQueue 定位 CommitLog 中的消息避免全量扫描存储过程全景图现在把各个模块串起来看消息的生命周期1.生产者发送消息到Broker2.Broker将消息顺序写入CommitLog3.异步线程同时构建ConsumeQueue和IndexFile4.消费者通过ConsumeQueue快速定位消息5.按需查询IndexFile实现消息回溯整个过程就像图书馆的管理系统•CommitLog是藏书库按入库时间摆放•ConsumeQueue是分类目录按题材/出版社分类•IndexFile是检索电脑支持按书名/作者查询2.4 设计优势•读写分离CommitLog 仅负责写入ConsumeQueue 负责查询提升并发性能•事务支持通过 CommitLog 中的事务状态标记 回查机制实现分布式事务消息•刷盘策略支持「异步刷盘高吞吐」「同步刷盘金融级可靠性」动态切换三、JMQ存储架构JMQ的消息存储分别参考了Kafka和RocketMQ存储设计上优点并根据京东内部的应用场景进行了改进和创新。3.1 核心存储模型分区日志 队列兼容JMQ存储的基本单元是PartitionGroup。在同一个Broker上每个PartitionGroup对应一组消息文件Journal Files顺序存放这个Topic的消息。与Kafka类似每个Topic包含若干Partition每个Partition对应一组索引文件Index Files索引中存放消息在消息文件中的位置和消息长度。消息写入时收到的消息按照对应的PartitionGroup写入依次追加写入消息文件中然后异步创建索引并写入对应Partition的索引文件中。以PartionGroup为基本存储单元的设计在兼顾灵活性的同时具有较好的性能并且单个PartitionGroup可以支持更多的并发。3.2 消息读/写过程写消息JMQ的写操作使用DirectBuffer作为缓存数据先写入DirectBuffer再异步通过FileChannel写入到文件中。•消息写入DirectBuffer后默认写入该节点成功数据的高可靠是通过Raft协议复制用多个内存副本来保证相对Kafka的写操作来看JMQ响应写入请求的处理过程没有发生系统调用在京东内部的大量单条同步发送的场景下开销更低、性能更优。•同时也避免使用MappedByteBufferMmap方式产生Page Fault中断OS在中断中将该页对应磁盘中的数据拷贝到内存中在对文件进行追加写入的情况下这一无法避免的过程是完全没有必要反而增加了写入的耗时的问题。读消息JMQ采用定长稠密索引设计每个索引固定长度。•定长设计的好处是直接根据索引序号就可以计算出索引在文件中的位置索引位置 索引序号 * 索引长度。这样消息的查找过程就比较简单了首先计算出索引所在的位置直接读取索引然后根据索引中记录的消息位置读取消息。•在京东内部应用场景中单条消息处理耗时高是比较常见的微服务架构下用户一般会申请更多的消费节点让每个消费节点单次拉取较小批量的消息进行处理以提升消费并行度这样消费拉取请求的次数会比较多稠密索引的设计会更适用内部的应用场景。JMQ消费读操作99%以上都能命中缓存JMQ设计的堆外内存与文件映射的一种缓存机制避免了Kafka可能遇到的Cache被污染影响性能和吞吐的问题。同时直接读内存也规避了RocketMQ在读取消息存储的日志数据文件时容易产生较多的随机访问读取磁盘影响性能的问题。当没有命中缓存时会默认降级为通过Mmap的方式读取消息。四、竞品对比分析JMQKafka存储模型以PartitionGroup为基本存储单元支持高并发写入 以Partition为基本存储单元支持灵活的数据复制和迁移消息写入性能- 单副本异步写入性能与 Kafka 相当 - 三副本异步写入性能优于 Kafka - 单副本异步写入性能与 JMQ 相当 - 三副本异步写入性能略低于 JMQ同步写入性能- 同步写入性能稳定几乎不受网络延迟影响 - 同步写入性能受网络延迟影响较大稳定性略逊于 JMQ多分区性能- 多分区异步写入性能与 Kafka 相当 - 同步写入性能略低于 Kafka - 多分区同步写入性能更稳定适合高并发场景副本机制支持异步复制副本间数据同步性能较好 支持异步和同步复制副本机制成熟适合复杂部署跨机房部署- 同步写入性能基本不受影响 - 异步写入性能下降 - 同步写入性能受网络延迟影响较大 - 异步写入性能下降适用场景- 对同步写入性能要求高 - 副本异步吞吐要求高 - 大规模微服务集群 - 复杂分区的高并发同步写入 - 大规模分布式系统 - 多语言生态支持丰富在单副本场景下JMQ与Kafka的单机写入性能均十分出色均可达到网络带宽上限。然而在更贴近生产环境的三副本场景中两者特性出现分化JMQ在三副本异步写入下的极限吞吐优势明显且在跨机房部署时其同步写入性能表现良好几乎不受网络延迟影响而Kafka则在多分区同步写入场景下展现出更稳定的性能衰减小于JMQ。在大部分异步吞吐场景及不同消息体下的性能趋势上两者表现相当。综上所述JMQ尤其适合对同步写入性能和副本异步吞吐有极高要求的场景而Kafka在复杂分区的高并发同步写入方面适应性更广。