06-09-A-Kafka架构与存储原理详解

06-09-A-Kafka架构与存储原理详解

️ 关键词:Kafka · Partition · Replica · Segment · 稀疏索引 · 顺序写 · 零拷贝 · PageCache · 批量压缩 · ZooKeeper/KRaft

📌 导读 :如果说 RocketMQ 是"业务消息专家",那 Kafka 就是"流处理管道之王 "------日志采集、行为埋点、大数据实时计算,几乎都跑在 Kafka 上。Kafka 的核心设计目标是极致吞吐 :百万级 TPS、单机百万消息/秒,靠的是四个"反直觉"的设计------磁盘顺序写比内存随机写快、零拷贝省 CPU、PageCache 当缓存、批量+压缩省网络 。Kafka 系列共四篇:本篇讲"架构与存储"(Broker/Partition/Replica 数据模型、Segment 分段与稀疏索引、高性能四设计、ZooKeeper→KRaft 演进),看完本篇你能做到:被问"Kafka 为什么快"能讲出四个原因,被问"Partition 怎么存的"能画出 Segment+稀疏索引结构,被问"KRaft 是什么"能讲出去 ZooKeeper 的动机。


📑 目录

  • 06-09-A-Kafka架构与存储原理详解
    • [📖 术语速查表(每个词都用人话解释)](#📖 术语速查表(每个词都用人话解释))
    • [一、Kafka 的定位:流处理管道之王](#一、Kafka 的定位:流处理管道之王)
      • [1.1 与 RocketMQ 的分工](#1.1 与 RocketMQ 的分工)
      • [1.2 Kafka 的核心设计目标:极致吞吐](#1.2 Kafka 的核心设计目标:极致吞吐)
    • 二、数据模型:Topic、Partition、Replica
      • [2.1 层级结构](#2.1 层级结构)
      • [2.2 Partition 的存储结构:Segment 分段](#2.2 Partition 的存储结构:Segment 分段)
      • [2.3 稀疏索引:查找消息的两级定位](#2.3 稀疏索引:查找消息的两级定位)
      • [2.4 与 RocketMQ 存储的对比](#2.4 与 RocketMQ 存储的对比)
    • [三、高性能:Kafka 为什么这么快](#三、高性能:Kafka 为什么这么快)
      • [3.1 四个"反直觉"设计](#3.1 四个"反直觉"设计)
      • [3.2 零拷贝详解](#3.2 零拷贝详解)
      • [3.3 PageCache 的妙用](#3.3 PageCache 的妙用)
      • [3.4 批量+压缩的复利效应](#3.4 批量+压缩的复利效应)
    • [四、集群协调:从 ZooKeeper 到 KRaft](#四、集群协调:从 ZooKeeper 到 KRaft)
      • [4.1 ZooKeeper 时代(≤2.8)](#4.1 ZooKeeper 时代(≤2.8))
      • [4.2 KRaft 时代(3.3+ 生产可用)](#4.2 KRaft 时代(3.3+ 生产可用))
    • [五、跑一遍:从建 Topic 到看 Segment 文件](#五、跑一遍:从建 Topic 到看 Segment 文件)
      • [5.1 启动与建 Topic(命令行)](#5.1 启动与建 Topic(命令行))
      • [5.2 生产与消费](#5.2 生产与消费)
      • [5.3 看磁盘上的 Segment 文件](#5.3 看磁盘上的 Segment 文件)
    • 六、总结
      • [6.1 一张图回顾全文](#6.1 一张图回顾全文)
      • [6.2 核心要点浓缩(十二条)](#6.2 核心要点浓缩(十二条))

📖 术语速查表(每个词都用人话解释)

** 数据模型类**

术语 一句话白话解释
Broker Kafka 的服务节点------存储消息、服务生产/消费请求,多个 Broker 组成集群
Topic 消息的主题/分类------一类消息一个 Topic(如 user-behavior、order-event)
Partition(分区) Topic 的物理分片------一个 Topic 分成多个 Partition,分布在不同 Broker------Partition 是并行和顺序的单位
Replica(副本) Partition 的备份------每个 Partition 有多个副本,分布在不同 Broker------Leader 读写,Follower 同步备份
Leader / Follower Partition 副本的角色------Leader 处理读写,Follower 只从 Leader 拉数据同步
Offset 消息在 Partition 内的单调递增序号------消费者按 Offset 顺序消费,Offset 是消费位点
Segment Partition 日志的物理分段------一个 Partition 由多个 Segment 文件组成,每个 Segment 含 .log(数据)+.index(偏移索引)+.timeindex(时间索引)
稀疏索引 .index 文件每隔 N 条消息(默认 4KB 间隔)记一个 Offset→物理位置映射------二分查找定位,不用每条都索引
Controller 集群的"管理者"------负责 Partition Leader 选举、Broker 上下线感知------一个 Broker 兼任
ZooKeeper Kafka 3.x 之前的协调者------存集群元数据、选 Controller;3.x+ 被 KRaft 替代
KRaft(Kafka Raft) 3.x+ 的自管理元数据模式------用内置 Raft 协议替代 ZooKeeper,元数据存在 Kafka 自己的 Topic 里

⚡ 高性能类

术语 一句话白话解释
顺序写 消息追加写入 Partition 日志文件末尾------磁盘顺序写速度接近内存随机写(~600MB/s vs ~100KB/s)
零拷贝(Zero Copy) 数据从磁盘到网卡不经过用户态 ------sendfile 系统调用直接在内核态完成 DMA 传输,省掉两次 CPU 拷贝+两次上下文切换
PageCache 操作系统的文件缓存 ------Kafka 不自己管缓存,靠 OS 的 PageCache------写入先写 PageCache(内存速度),读取命中 PageCache 不读磁盘
批量发送(Batch) Producer 把多条消息攒成一批 发送------减少网络请求次数,摊薄开销
压缩 Producer 端压缩(snappy/lz4/zstd)------减少网络传输量,Broker 存压缩数据,Consumer 解压
RecordAccumulator Producer 的消息累加器------按 Partition 攒批(batch.size 或 linger.ms 触发发送)

一、Kafka 的定位:流处理管道之王

1.1 与 RocketMQ 的分工

#mermaid-svg-TBAI17fZoMR97BCR{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TBAI17fZoMR97BCR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TBAI17fZoMR97BCR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TBAI17fZoMR97BCR .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-TBAI17fZoMR97BCR .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-TBAI17fZoMR97BCR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TBAI17fZoMR97BCR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TBAI17fZoMR97BCR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TBAI17fZoMR97BCR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TBAI17fZoMR97BCR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TBAI17fZoMR97BCR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TBAI17fZoMR97BCR .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-TBAI17fZoMR97BCR .marker.cross{stroke:#0b0b0b;}#mermaid-svg-TBAI17fZoMR97BCR svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-TBAI17fZoMR97BCR p{margin:0;}#mermaid-svg-TBAI17fZoMR97BCR .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-TBAI17fZoMR97BCR .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-TBAI17fZoMR97BCR .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-TBAI17fZoMR97BCR .cluster-label span p{background-color:transparent;}#mermaid-svg-TBAI17fZoMR97BCR .label text,#mermaid-svg-TBAI17fZoMR97BCR span{fill:#333;color:#333;}#mermaid-svg-TBAI17fZoMR97BCR .node rect,#mermaid-svg-TBAI17fZoMR97BCR .node circle,#mermaid-svg-TBAI17fZoMR97BCR .node ellipse,#mermaid-svg-TBAI17fZoMR97BCR .node polygon,#mermaid-svg-TBAI17fZoMR97BCR .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-TBAI17fZoMR97BCR .rough-node .label text,#mermaid-svg-TBAI17fZoMR97BCR .node .label text,#mermaid-svg-TBAI17fZoMR97BCR .image-shape .label,#mermaid-svg-TBAI17fZoMR97BCR .icon-shape .label{text-anchor:middle;}#mermaid-svg-TBAI17fZoMR97BCR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TBAI17fZoMR97BCR .rough-node .label,#mermaid-svg-TBAI17fZoMR97BCR .node .label,#mermaid-svg-TBAI17fZoMR97BCR .image-shape .label,#mermaid-svg-TBAI17fZoMR97BCR .icon-shape .label{text-align:center;}#mermaid-svg-TBAI17fZoMR97BCR .node.clickable{cursor:pointer;}#mermaid-svg-TBAI17fZoMR97BCR .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-TBAI17fZoMR97BCR .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-TBAI17fZoMR97BCR .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-TBAI17fZoMR97BCR .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-TBAI17fZoMR97BCR .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-TBAI17fZoMR97BCR .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-TBAI17fZoMR97BCR .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-TBAI17fZoMR97BCR .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-TBAI17fZoMR97BCR .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-TBAI17fZoMR97BCR .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-TBAI17fZoMR97BCR .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-TBAI17fZoMR97BCR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TBAI17fZoMR97BCR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TBAI17fZoMR97BCR rect.text{fill:none;stroke-width:0;}#mermaid-svg-TBAI17fZoMR97BCR .icon-shape,#mermaid-svg-TBAI17fZoMR97BCR .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-TBAI17fZoMR97BCR .icon-shape p,#mermaid-svg-TBAI17fZoMR97BCR .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-TBAI17fZoMR97BCR .icon-shape .label rect,#mermaid-svg-TBAI17fZoMR97BCR .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-TBAI17fZoMR97BCR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TBAI17fZoMR97BCR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TBAI17fZoMR97BCR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 消息队列选型。
业务消息。
订单 / 交易 / 事务 / 延迟。
→ RocketMQ。
事务消息、延迟消息原生支持。

日志 / 流处理。
行为埋点 / 日志采集 / 大数据。
→ Kafka。
百万 TPS,大数据生态,流计算。

轻量业务。
中小规模,路由灵活。
→ RabbitMQ。
AMQP,死信交换机,延迟队列。

Kafka 的生态位 :Flink/Spark 流计算的标配输入源、日志采集(Filebeat→Kafka→ES/ClickHouse)的中间管道、CDC(binlog 同步)的传输层------"数据管道"场景 Kafka 是事实标准(详见 26-A 篇三大对比)。

1.2 Kafka 的核心设计目标:极致吞吐

设计 目标
Partition 并行 Topic 分多个 Partition,分布在不同 Broker------并行读写,水平扩展
顺序写 消息追加写 Partition 日志------磁盘顺序写接近内存速度
零拷贝 sendfile 内核态传输------省 CPU 拷贝
PageCache 靠 OS 文件缓存------写入内存速度,读取命中缓存
批量+压缩 攒批发送+压缩------省网络请求和带宽

一句话 :Kafka 为"高吞吐日志/流数据"而生------四个"反直觉"设计(顺序写/零拷贝/PageCache/批量压缩)叠出百万级 TPS。


二、数据模型:Topic、Partition、Replica

2.1 层级结构

#mermaid-svg-591DW555YA7TyCrx{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-591DW555YA7TyCrx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-591DW555YA7TyCrx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-591DW555YA7TyCrx .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-591DW555YA7TyCrx .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-591DW555YA7TyCrx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-591DW555YA7TyCrx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-591DW555YA7TyCrx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-591DW555YA7TyCrx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-591DW555YA7TyCrx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-591DW555YA7TyCrx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-591DW555YA7TyCrx .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-591DW555YA7TyCrx .marker.cross{stroke:#0b0b0b;}#mermaid-svg-591DW555YA7TyCrx svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-591DW555YA7TyCrx p{margin:0;}#mermaid-svg-591DW555YA7TyCrx .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-591DW555YA7TyCrx .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-591DW555YA7TyCrx .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-591DW555YA7TyCrx .cluster-label span p{background-color:transparent;}#mermaid-svg-591DW555YA7TyCrx .label text,#mermaid-svg-591DW555YA7TyCrx span{fill:#333;color:#333;}#mermaid-svg-591DW555YA7TyCrx .node rect,#mermaid-svg-591DW555YA7TyCrx .node circle,#mermaid-svg-591DW555YA7TyCrx .node ellipse,#mermaid-svg-591DW555YA7TyCrx .node polygon,#mermaid-svg-591DW555YA7TyCrx .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-591DW555YA7TyCrx .rough-node .label text,#mermaid-svg-591DW555YA7TyCrx .node .label text,#mermaid-svg-591DW555YA7TyCrx .image-shape .label,#mermaid-svg-591DW555YA7TyCrx .icon-shape .label{text-anchor:middle;}#mermaid-svg-591DW555YA7TyCrx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-591DW555YA7TyCrx .rough-node .label,#mermaid-svg-591DW555YA7TyCrx .node .label,#mermaid-svg-591DW555YA7TyCrx .image-shape .label,#mermaid-svg-591DW555YA7TyCrx .icon-shape .label{text-align:center;}#mermaid-svg-591DW555YA7TyCrx .node.clickable{cursor:pointer;}#mermaid-svg-591DW555YA7TyCrx .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-591DW555YA7TyCrx .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-591DW555YA7TyCrx .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-591DW555YA7TyCrx .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-591DW555YA7TyCrx .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-591DW555YA7TyCrx .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-591DW555YA7TyCrx .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-591DW555YA7TyCrx .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-591DW555YA7TyCrx .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-591DW555YA7TyCrx .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-591DW555YA7TyCrx .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-591DW555YA7TyCrx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-591DW555YA7TyCrx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-591DW555YA7TyCrx rect.text{fill:none;stroke-width:0;}#mermaid-svg-591DW555YA7TyCrx .icon-shape,#mermaid-svg-591DW555YA7TyCrx .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-591DW555YA7TyCrx .icon-shape p,#mermaid-svg-591DW555YA7TyCrx .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-591DW555YA7TyCrx .icon-shape .label rect,#mermaid-svg-591DW555YA7TyCrx .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-591DW555YA7TyCrx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-591DW555YA7TyCrx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-591DW555YA7TyCrx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Topic:user-behavior。
Partition 0。
Broker1,Leader。
Broker2,Follower。
Broker3,Follower。

Partition 内消息按 Offset 顺序
0, 1, 2, 3...... 追加写入,不可变。

Partition 1。
Broker2,Leader。
Broker3,Follower。
Broker1,Follower。

Partition 2。
Broker3,Leader。
Broker1,Follower。
Broker2,Follower。

三个关键概念:

  1. Partition 是并行的单位 :一个 Topic 分多个 Partition,分布在不同 Broker------Producer 并行写不同 Partition,Consumer 并行消费不同 Partition;
  2. Partition 内有序 :同一 Partition 内消息按 Offset 顺序------需要顺序的消息放同一 Partition (按 Key 哈希);Partition 之间无序;
  3. Replica 是高可用的单位 :每个 Partition 有多个副本------Leader 读写,Follower 同步备份(ISR 机制 07 篇详解)。

2.2 Partition 的存储结构:Segment 分段

text 复制代码
Partition 目录(topic-partition 命名):
user-behavior-0/
├── 00000000000000000000.log       ← Segment 数据文件(消息体,默认 1GB 滚动)
├── 00000000000000000000.index     ← 偏移索引(Offset → 文件物理位置)
├── 00000000000000000000.timeindex ← 时间索引(时间戳 → Offset)
├── 00000000000000368754.log       ← 第二个 Segment(文件名 = 起始 Offset)
├── 00000000000000368754.index
├── 00000000000000368754.timeindex
├── leader-epoch-checkpoint        ← Leader 纪元(故障恢复用)
└── partition.metadata             ← Topic ID

Segment 分段的意义:

设计 好处
一个 Partition 切多个 Segment 删除过期数据 = 直接删整个 Segment 文件(不用像 LSM 那样合并清理)
文件名 = 起始 Offset 定位 Segment 用二分查找文件名,不用扫文件内容
活跃 Segment 只有一个 写入永远在最后一个 Segment 追加------顺序写

2.3 稀疏索引:查找消息的两级定位

查找 Offset=368800 的消息:

text 复制代码
① 二分查找 Segment 文件名:
   [0, 368754, 737500...] → 368800 落在 00000000000000368754.log
② 在 .index 里二分查找稀疏索引项:
   (368754 → 0), (368850 → 4521), (368946 → 9040)...
   → 找到 ≤368800 的最大项 (368754 → 0),从物理位置 0 开始
③ 从该位置顺序扫 .log,逐条比对 Offset 直到 368800

为什么稀疏而不是全量索引:

维度 稀疏索引(Kafka) 全量索引
索引大小 小(每 4KB 日志一条索引,1GB Segment 只有 ~25 万条×12 字节) 大(每条消息一项)
内存占用 整个 .index 可放内存/mmap 放不下
查找速度 二分 + 短距离顺序扫(顺序扫很快) 二分一步到位
结论 索引小到能常驻内存,比全量索引整体更快 ---

一句话 :Segment 管"分而治之"(文件名二分定位+过期整段删除),稀疏索引管"小而常驻"(.index 全放内存,二分+短扫)------两级定位让 Kafka 在 TB 级 Partition 上查任意 Offset 都是微秒级。

2.4 与 RocketMQ 存储的对比

维度 Kafka RocketMQ
写入单位 每个 Partition 独立日志文件 所有 Topic 混写一个 CommitLog
Topic 多的影响 Partition 总数大 → 多文件并发追加 → 磁盘退化为随机写 Topic 数量不影响写入(永远单文件顺序写)
消费索引 Partition 日志本身就是"队列"(Offset 连续) ConsumeQueue 二级索引(20 字节/条)
读性能 顺序读 Partition 日志(消费路径最短) 先读 ConsumeQueue 再读 CommitLog(多一跳)
适用 少 Topic 大吞吐(日志/流) 多 Topic 业务消息(一个业务一个 Topic)

这就是"Kafka 适合日志流、RocketMQ 适合业务消息"的存储层根源------Kafka 单 Partition 吞吐极高但 Partition 总数敏感(上万后性能下降),RocketMQ 万级 Topic 无压力但单 Topic 极限吞吐略低。


三、高性能:Kafka 为什么这么快

3.1 四个"反直觉"设计

#mermaid-svg-MuaUgdwnpS8QnTE5{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MuaUgdwnpS8QnTE5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MuaUgdwnpS8QnTE5 .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-MuaUgdwnpS8QnTE5 .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-MuaUgdwnpS8QnTE5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MuaUgdwnpS8QnTE5 .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-MuaUgdwnpS8QnTE5 .marker.cross{stroke:#0b0b0b;}#mermaid-svg-MuaUgdwnpS8QnTE5 svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-MuaUgdwnpS8QnTE5 p{margin:0;}#mermaid-svg-MuaUgdwnpS8QnTE5 .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-MuaUgdwnpS8QnTE5 .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-MuaUgdwnpS8QnTE5 .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-MuaUgdwnpS8QnTE5 .cluster-label span p{background-color:transparent;}#mermaid-svg-MuaUgdwnpS8QnTE5 .label text,#mermaid-svg-MuaUgdwnpS8QnTE5 span{fill:#333;color:#333;}#mermaid-svg-MuaUgdwnpS8QnTE5 .node rect,#mermaid-svg-MuaUgdwnpS8QnTE5 .node circle,#mermaid-svg-MuaUgdwnpS8QnTE5 .node ellipse,#mermaid-svg-MuaUgdwnpS8QnTE5 .node polygon,#mermaid-svg-MuaUgdwnpS8QnTE5 .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-MuaUgdwnpS8QnTE5 .rough-node .label text,#mermaid-svg-MuaUgdwnpS8QnTE5 .node .label text,#mermaid-svg-MuaUgdwnpS8QnTE5 .image-shape .label,#mermaid-svg-MuaUgdwnpS8QnTE5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-MuaUgdwnpS8QnTE5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MuaUgdwnpS8QnTE5 .rough-node .label,#mermaid-svg-MuaUgdwnpS8QnTE5 .node .label,#mermaid-svg-MuaUgdwnpS8QnTE5 .image-shape .label,#mermaid-svg-MuaUgdwnpS8QnTE5 .icon-shape .label{text-align:center;}#mermaid-svg-MuaUgdwnpS8QnTE5 .node.clickable{cursor:pointer;}#mermaid-svg-MuaUgdwnpS8QnTE5 .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-MuaUgdwnpS8QnTE5 .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-MuaUgdwnpS8QnTE5 .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-MuaUgdwnpS8QnTE5 .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-MuaUgdwnpS8QnTE5 .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-MuaUgdwnpS8QnTE5 .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-MuaUgdwnpS8QnTE5 .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-MuaUgdwnpS8QnTE5 .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-MuaUgdwnpS8QnTE5 .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-MuaUgdwnpS8QnTE5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MuaUgdwnpS8QnTE5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MuaUgdwnpS8QnTE5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-MuaUgdwnpS8QnTE5 .icon-shape,#mermaid-svg-MuaUgdwnpS8QnTE5 .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-MuaUgdwnpS8QnTE5 .icon-shape p,#mermaid-svg-MuaUgdwnpS8QnTE5 .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-MuaUgdwnpS8QnTE5 .icon-shape .label rect,#mermaid-svg-MuaUgdwnpS8QnTE5 .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-MuaUgdwnpS8QnTE5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MuaUgdwnpS8QnTE5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MuaUgdwnpS8QnTE5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Kafka 为什么快。
① 顺序写磁盘 消息追加写 Part
ition 日志末尾 磁盘顺序写~600MB / s
接近内存随机写~100MB / s → 磁盘不是瓶颈。

② 零拷贝 sendfile
数据从磁盘到网卡不经过用户态
省 2 次 CPU 拷贝+2 次上下文切换
→ CPU 不浪费在搬数据上。

③ PageCache
写入先写 OS 文件缓存,内存速度
读取命中 PageCache 不读磁盘
→ Kafka 不管缓存,OS 管。

④ 批量+压缩 Producer 攒
批发送,减少网络请求 snappy / lz4 压缩,
减少带宽 → 网络不是瓶颈。

3.2 零拷贝详解

传统 IO(4 次拷贝 + 4 次上下文切换):

text 复制代码
磁盘 → [DMA] → 内核缓冲区 → [CPU] → 用户缓冲区 → [CPU] → Socket缓冲区 → [DMA] → 网卡
         拷贝1                拷贝2              拷贝3              拷贝4

零拷贝 sendfile(2 次拷贝 + 2 次上下文切换):

text 复制代码
磁盘 → [DMA] → 内核缓冲区 → [DMA] → 网卡
         拷贝1                拷贝2(CPU不参与数据搬运)

省掉的 :2 次 CPU 拷贝(内核↔用户)+ 2 次上下文切换------CPU 不搬数据,只做控制。

Kafka 消费路径为什么天然适合零拷贝 :Consumer 拉消息 = "从日志文件原样发到网卡"------数据不需要加工(不解析、不过滤),sendfile 一步到位。对比 RocketMQ 要按 ConsumeQueue 索引跳读 CommitLog、还要按 Tag 过滤------数据要经过用户态加工,所以选 mmap(06-01-A 篇 4.2)。

3.3 PageCache 的妙用

设计 说明
写入 消息写入 PageCache(内存)即返回------内存速度,后台异步刷盘
读取 热数据在 PageCache------命中缓存不读磁盘;冷数据读磁盘后也进 PageCache
为什么不用 JVM 堆缓存 JVM 堆缓存要 GC、对象头开销大、序列化/反序列化------PageCache 是 OS 管理的字节缓存,无 GC、无对象开销
重启不丢 PageCache 是 OS 的,Kafka 重启后 OS 还在------缓存不冷启动

一句话 :Kafka 把"缓存"这件事外包给 OS 的 PageCache------无 GC、无对象开销、重启不冷启动,比自己管缓存聪明得多。

3.4 批量+压缩的复利效应

text 复制代码
不批量:1000 条消息 = 1000 次网络请求(每次 TCP 往返 ~1ms)→ 1 秒发完
批量:  1000 条攒成 10 批(batch.size=16KB)= 10 次请求 → 10ms 发完
压缩:  lz4 压缩比 ~3:1 → 网络传输量再降 2/3

复利:网络请求数 ÷100 × 传输量 ÷3 = 吞吐提升一个数量级

端到端压缩 :Producer 压缩整批 → Broker 原样存储压缩批次(不解压!) → Consumer 拉取后自己解压------压缩块从生产到消费只压一次、只解一次,Broker CPU 几乎不参与(这也是零拷贝能成立的前提:Broker 不需要理解消息内容)。


四、集群协调:从 ZooKeeper 到 KRaft

4.1 ZooKeeper 时代(≤2.8)

ZK 承担的职责 说明
Controller 选举 抢注临时节点,谁抢到谁是 Controller
集群元数据 Topic/Partition/ISR 列表、Broker 注册
故障感知 Broker 掉线 → 临时节点消失 → 触发 Leader 选举

痛点:

  1. 元数据扩展性差 :Partition 数十万级时,ZK 存全量元数据 + Controller 故障切换要全量加载元数据(分钟级);
  2. 多一套集群:ZK 独立部署运维(3/5 节点),和 Kafka 版本兼容性要小心;
  3. Controller 与 ZK 双写:元数据变更要经过 Controller→ZK→广播,链路长。

4.2 KRaft 时代(3.3+ 生产可用)

#mermaid-svg-Q8k7mmC8jyHJ9tTq{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .marker.cross{stroke:#0b0b0b;}#mermaid-svg-Q8k7mmC8jyHJ9tTq svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-Q8k7mmC8jyHJ9tTq p{margin:0;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .cluster-label span p{background-color:transparent;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .label text,#mermaid-svg-Q8k7mmC8jyHJ9tTq span{fill:#333;color:#333;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .node rect,#mermaid-svg-Q8k7mmC8jyHJ9tTq .node circle,#mermaid-svg-Q8k7mmC8jyHJ9tTq .node ellipse,#mermaid-svg-Q8k7mmC8jyHJ9tTq .node polygon,#mermaid-svg-Q8k7mmC8jyHJ9tTq .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .rough-node .label text,#mermaid-svg-Q8k7mmC8jyHJ9tTq .node .label text,#mermaid-svg-Q8k7mmC8jyHJ9tTq .image-shape .label,#mermaid-svg-Q8k7mmC8jyHJ9tTq .icon-shape .label{text-anchor:middle;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .rough-node .label,#mermaid-svg-Q8k7mmC8jyHJ9tTq .node .label,#mermaid-svg-Q8k7mmC8jyHJ9tTq .image-shape .label,#mermaid-svg-Q8k7mmC8jyHJ9tTq .icon-shape .label{text-align:center;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .node.clickable{cursor:pointer;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-Q8k7mmC8jyHJ9tTq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Q8k7mmC8jyHJ9tTq rect.text{fill:none;stroke-width:0;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .icon-shape,#mermaid-svg-Q8k7mmC8jyHJ9tTq .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .icon-shape p,#mermaid-svg-Q8k7mmC8jyHJ9tTq .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .icon-shape .label rect,#mermaid-svg-Q8k7mmC8jyHJ9tTq .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-Q8k7mmC8jyHJ9tTq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Q8k7mmC8jyHJ9tTq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Q8k7mmC8jyHJ9tTq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} KRaft 模式。
① 元数据存进 Kafka 自己的
内部 Topic(__cluster_metadata)。
------ Kafka 用自己的日志
存自己的元数据。

② Controller 是 Quorum 投票组。
3 / 5 个 Controller 节点,
Raft 协议。
Active Controller 挂了
秒级选出新 Controller。

③ 元数据变更 = 追加日志。
Broker 增量拉取日志同步元数据。
------ 不再全量加载,
新 Broker 加入秒级就绪。

维度 ZooKeeper 模式 KRaft 模式
外部依赖 要部署 ZK 集群 无(自包含)
元数据存储 ZK 树形节点 内部 Topic(日志)
Controller 故障切换 全量加载元数据(分钟级) 增量日志同步(秒级)
Partition 扩展上限 ~20 万(受 ZK 限制) 百万级
运维 两套集群 一套集群

一句话 :KRaft = "Kafka 用自己管自己"------元数据变成日志、Controller 变成 Raft 组,去掉了 ZooKeeper 这个"外挂大脑",扩展性和运维性双升。


五、跑一遍:从建 Topic 到看 Segment 文件

下面是可以直接照做的实操示例(单机 Kafka 3.x KRaft 模式),每一步给出真实结果。

5.1 启动与建 Topic(命令行)

shell 复制代码
# ① KRaft 模式单机启动(无需 ZooKeeper)
bin/kafka-storage.sh random-uuid   # 生成集群 UUID
# 输出:kafka-storage.sh random-uuid
# 6N9d3nlqR7eKxYtJ2vB4cA

bin/kafka-storage.sh format -t 6N9d3nlqR7eKxYtJ2vB4cA -c config/kraft/server.properties
# 输出:Formatting /tmp/kraft-combined-logs with metadata.version 3.6-ivr02...

bin/kafka-server-start.sh config/kraft/server.properties
# 输出:[KafkaServer id=1] started (kafka.server.KafkaServer)

# ② 创建 Topic:3 个 Partition,2 副本
bin/kafka-topics.sh --bootstrap-server localhost:9092 \
  --create --topic user-behavior --partitions 3 --replication-factor 1
# 输出:Created topic user-behavior.

# ③ 查看 Topic 详情
bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic user-behavior

③ 的输出(3 个 Partition,Leader 都在 Broker 1,ISR 各 1 个副本):

text 复制代码
Topic: user-behavior  TopicId: Xy7...  PartitionCount: 3  ReplicationFactor: 1
    Topic: user-behavior  Partition: 0  Leader: 1  Replicas: 1  Isr: 1
    Topic: user-behavior  Partition: 1  Leader: 1  Replicas: 1  Isr: 1
    Topic: user-behavior  Partition: 2  Leader: 1  Replicas: 1  Isr: 1

5.2 生产与消费

shell 复制代码
# ④ 生产消息(带 Key,观察分区路由)
bin/kafka-console-producer.sh --bootstrap-server localhost:9092 \
  --topic user-behavior --property parse.key=true --property key.separator=:
> user1001:{"action":"click","item":"sku-88"}
> user1002:{"action":"buy","item":"sku-90"}
> user1001:{"action":"cart","item":"sku-88"}

# ⑤ 消费消息(打印 Partition 和 Offset)
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --topic user-behavior --from-beginning \
  --property print.partition=true --property print.offset=true --property print.key=true

⑤ 的消费结果(同 Key 进同 Partition,Offset 在 Partition 内单调递增):

text 复制代码
Partition:0  Offset:0  user1002  {"action":"buy","item":"sku-90"}
Partition:1  Offset:0  user1001  {"action":"click","item":"sku-88"}
Partition:1  Offset:1  user1001  {"action":"cart","item":"sku-88"}

💡 对照理解 :user1001 的两条消息都落在 Partition 1 且 Offset 连续(0→1)------按 Key 哈希路由保证"同 Key 同 Partition + Partition 内有序" ,这就是 Kafka 顺序消息的全部机制(06 篇详解);user1002 哈希到 Partition 0------Partition 之间没有顺序关系。

5.3 看磁盘上的 Segment 文件

shell 复制代码
# ⑥ 查看 Partition 目录
ls -lh /tmp/kraft-combined-logs/user-behavior-1/

⑥ 的真实输出(.log + .index + .timeindex 三件套):

text 复制代码
-rw-r--r-- 1 root root   0 Jan  1 10:00 00000000000000000000.index
-rw-r--r-- 1 root root 176 Jan  1 10:02 00000000000000000000.log
-rw-r--r-- 1 root root   0 Jan  1 10:00 00000000000000000000.timeindex
-rw-r--r-- 1 root root   8 Jan  1 10:00 leader-epoch-checkpoint
-rw-r--r-- 1 root root  43 Jan  1 10:00 partition.metadata

# ⑦ 用 DumpLogSegments 工具解析 .log 内容
bin/kafka-dump-log.sh --files /tmp/kraft-combined-logs/user-behavior-1/00000000000000000000.log --print-data-log

⑦ 的解析结果(能看到 CRC/Offset/Key/Value 的完整批次结构):

text 复制代码
Dumping /tmp/kraft-combined-logs/user-behavior-1/00000000000000000000.log
Starting offset: 0
baseOffset: 0 lastOffset: 0 count: 1 baseSequence: -1 ...
| offset: 0 CreateTime: 1704067200000 keySize: 8 valueSize: 34
  payload: {"action":"click","item":"sku-88"}
baseOffset: 1 lastOffset: 1 count: 1 ...
| offset: 1 CreateTime: 1704067260000 keySize: 8 valueSize: 33
  payload: {"action":"cart","item":"sku-88"}

观察点 :消息以**批次(RecordBatch)**为单位存储(baseOffset/lastOffset/count)------这就是 3.4 节"批量发送"在磁盘上的形态;.index 当前为 0 字节------消息太少还没达到 log.index.interval.bytes=4KB 的稀疏索引写入间隔。


六、总结

6.1 一张图回顾全文

#mermaid-svg-2fjKw9CO1PIQ7YtN{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2fjKw9CO1PIQ7YtN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2fjKw9CO1PIQ7YtN .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-2fjKw9CO1PIQ7YtN .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-2fjKw9CO1PIQ7YtN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2fjKw9CO1PIQ7YtN .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-2fjKw9CO1PIQ7YtN .marker.cross{stroke:#0b0b0b;}#mermaid-svg-2fjKw9CO1PIQ7YtN svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-2fjKw9CO1PIQ7YtN p{margin:0;}#mermaid-svg-2fjKw9CO1PIQ7YtN .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-2fjKw9CO1PIQ7YtN .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-2fjKw9CO1PIQ7YtN .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-2fjKw9CO1PIQ7YtN .cluster-label span p{background-color:transparent;}#mermaid-svg-2fjKw9CO1PIQ7YtN .label text,#mermaid-svg-2fjKw9CO1PIQ7YtN span{fill:#333;color:#333;}#mermaid-svg-2fjKw9CO1PIQ7YtN .node rect,#mermaid-svg-2fjKw9CO1PIQ7YtN .node circle,#mermaid-svg-2fjKw9CO1PIQ7YtN .node ellipse,#mermaid-svg-2fjKw9CO1PIQ7YtN .node polygon,#mermaid-svg-2fjKw9CO1PIQ7YtN .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-2fjKw9CO1PIQ7YtN .rough-node .label text,#mermaid-svg-2fjKw9CO1PIQ7YtN .node .label text,#mermaid-svg-2fjKw9CO1PIQ7YtN .image-shape .label,#mermaid-svg-2fjKw9CO1PIQ7YtN .icon-shape .label{text-anchor:middle;}#mermaid-svg-2fjKw9CO1PIQ7YtN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2fjKw9CO1PIQ7YtN .rough-node .label,#mermaid-svg-2fjKw9CO1PIQ7YtN .node .label,#mermaid-svg-2fjKw9CO1PIQ7YtN .image-shape .label,#mermaid-svg-2fjKw9CO1PIQ7YtN .icon-shape .label{text-align:center;}#mermaid-svg-2fjKw9CO1PIQ7YtN .node.clickable{cursor:pointer;}#mermaid-svg-2fjKw9CO1PIQ7YtN .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-2fjKw9CO1PIQ7YtN .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-2fjKw9CO1PIQ7YtN .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-2fjKw9CO1PIQ7YtN .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-2fjKw9CO1PIQ7YtN .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-2fjKw9CO1PIQ7YtN .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-2fjKw9CO1PIQ7YtN .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-2fjKw9CO1PIQ7YtN .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-2fjKw9CO1PIQ7YtN .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-2fjKw9CO1PIQ7YtN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2fjKw9CO1PIQ7YtN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2fjKw9CO1PIQ7YtN rect.text{fill:none;stroke-width:0;}#mermaid-svg-2fjKw9CO1PIQ7YtN .icon-shape,#mermaid-svg-2fjKw9CO1PIQ7YtN .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-2fjKw9CO1PIQ7YtN .icon-shape p,#mermaid-svg-2fjKw9CO1PIQ7YtN .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-2fjKw9CO1PIQ7YtN .icon-shape .label rect,#mermaid-svg-2fjKw9CO1PIQ7YtN .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-2fjKw9CO1PIQ7YtN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2fjKw9CO1PIQ7YtN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2fjKw9CO1PIQ7YtN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Kafka 架构与存储。
数据模型。
Topic → Partition → Replica。
Partition 并行 + 有序。
Segment 分段,文件名 = 起始 Offset。
稀疏索引两级定位。

高性能。
顺序写磁盘 ~600MB/s。
零拷贝 sendfile 省 CPU。
PageCache 外包 OS。
批量 + 压缩,端到端只压一次。

存储对比。
Kafka:每 Partition 独立日志。
RocketMQ:全 Topic 混写 CommitLog。
少 Topic 大吞吐选 Kafka。
多 Topic 业务消息选 RocketMQ。

集群协调。
ZooKeeper:外挂大脑,扩展性差。
KRaft:元数据即日志 + Raft 选主。
3.3+ 生产可用,Partition 百万级。

6.2 核心要点浓缩(十二条)

  1. Kafka 定位 :流处理管道之王------日志/埋点/大数据/CDC,百万级 TPS;业务消息(事务/延迟)选 RocketMQ,流处理选 Kafka。
  2. 数据模型 :Topic → Partition(并行+有序)→ Replica(高可用)------Partition 是并行和顺序的单位,Replica 是高可用的单位。
  3. Partition 内有序、之间无序 :需要顺序的消息按 Key 哈希进同一 Partition------这是 Kafka 顺序保证的全部。
  4. Segment 分段 :文件名=起始 Offset(二分定位),过期数据整段删除------活跃 Segment 只有一个,写入永远顺序追加。
  5. 稀疏索引 :每 4KB 日志一条索引项------.index 小到常驻内存,二分+短距离顺序扫,TB 级 Partition 查任意 Offset 微秒级。
  6. vs RocketMQ 存储 :Kafka 每 Partition 独立日志(Partition 总数敏感),RocketMQ 全 Topic 混写 CommitLog(Topic 数量不敏感)------存储结构决定适用场景。
  7. 高性能四设计 :顺序写(磁盘接近内存)、零拷贝(sendfile 省 CPU)、PageCache(外包 OS 缓存)、批量+压缩(省网络)------四个"反直觉"叠出百万 TPS。
  8. 零拷贝 :sendfile 内核态 DMA 传输------省 2 次 CPU 拷贝+2 次上下文切换;成立前提是 Broker 不加工消息(端到端压缩块原样存储)。
  9. PageCache 妙用 :写入内存速度、读取命中缓存、无 GC、重启不冷启动------Kafka 把缓存外包给 OS,比自己管聪明。
  10. 批量+压缩复利 :请求数 ÷100 × 传输量 ÷3 = 吞吐提升一个数量级------压缩块从 Producer 到 Consumer 只压一次解一次,Broker CPU 不参与。
  11. ZooKeeper 痛点:元数据扩展性差(Partition ~20 万上限)、Controller 切换全量加载(分钟级)、多运维一套集群。
  12. KRaft :元数据存内部 Topic(日志化)+ Controller Raft 组------去 ZooKeeper、秒级切换、Partition 百万级,3.3+ 生产可用。

📌 最后一句话 :Kafka 的设计哲学是"把能外包的都外包 "------缓存外包给 OS 的 PageCache(无 GC)、传输外包给内核的 sendfile(零拷贝)、顺序外包给磁盘的物理特性(顺序写快)、协调外包给 Raft(KRaft)。Kafka 自己只做最薄的一层:Partition 管理+副本同步+Offset 管理 ,其余全交给操作系统和硬件。这就是"少即是多"的架构智慧------做得越少,越快。


📌 配套阅读:

上一篇:《06-08-B-RocketMQ面试与生产事故实战.md》

下一篇:《06-10-A-Kafka消息特性与消费机制详解.md》

 B 篇:《06-16-B-Kafka面试与生产事故实战.md》

如果这篇文章对你有帮助,欢迎点赞、收藏、关注!

相关推荐
阳明山水1 小时前
CEDAR双重解耦实现决策与干预分离
人工智能·深度学习·算法·机器学习·架构
努力努力再努力wz1 小时前
【CUDA入门系列】CUDA 执行模型与性能优化:SM 调度、Occupancy、内存访问与 Bank Conflict
性能优化·架构
银河技术1 小时前
TB级文本去重实战:从单机 OOM 到 Spark / Ray 分布式架构的工程演进
分布式·微服务·重构·架构·spark·llm·rag
施棠海1 小时前
多 Agent 团队编排系统的设计与实测:agent-workflow
python·ai·架构
xcl09259 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
萧瑟余晖12 小时前
Dubbo SPI扩展机制详解
架构·dubbo
Shulex12 小时前
面向跨境电商多渠道消息系统的技术架构:亚马逊站内信合规对接与自动化执行链路设计
运维·架构·自动化
醉颜凉13 小时前
Kafka 与 RabbitMQ/RocketMQ 选型对比:场景匹配、性能基准与迁移成本
kafka·消息队列·rabbitmq·rocketmq·中间件选型
吴建旭 智宅焕14 小时前
AI搜索时代的智能家居交付知识架构:官网作为可信一手信息源与全国交付基础设施
人工智能·架构·智能家居