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。
三个关键概念:
- Partition 是并行的单位 :一个 Topic 分多个 Partition,分布在不同 Broker------Producer 并行写不同 Partition,Consumer 并行消费不同 Partition;
- Partition 内有序 :同一 Partition 内消息按 Offset 顺序------需要顺序的消息放同一 Partition (按 Key 哈希);Partition 之间无序;
- 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 选举 |
痛点:
- 元数据扩展性差 :Partition 数十万级时,ZK 存全量元数据 + Controller 故障切换要全量加载元数据(分钟级);
- 多一套集群:ZK 独立部署运维(3/5 节点),和 Kafka 版本兼容性要小心;
- 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 核心要点浓缩(十二条)
- Kafka 定位 :流处理管道之王------日志/埋点/大数据/CDC,百万级 TPS;业务消息(事务/延迟)选 RocketMQ,流处理选 Kafka。
- 数据模型 :Topic → Partition(并行+有序)→ Replica(高可用)------Partition 是并行和顺序的单位,Replica 是高可用的单位。
- Partition 内有序、之间无序 :需要顺序的消息按 Key 哈希进同一 Partition------这是 Kafka 顺序保证的全部。
- Segment 分段 :文件名=起始 Offset(二分定位),过期数据整段删除------活跃 Segment 只有一个,写入永远顺序追加。
- 稀疏索引 :每 4KB 日志一条索引项------.index 小到常驻内存,二分+短距离顺序扫,TB 级 Partition 查任意 Offset 微秒级。
- vs RocketMQ 存储 :Kafka 每 Partition 独立日志(Partition 总数敏感),RocketMQ 全 Topic 混写 CommitLog(Topic 数量不敏感)------存储结构决定适用场景。
- 高性能四设计 :顺序写(磁盘接近内存)、零拷贝(sendfile 省 CPU)、PageCache(外包 OS 缓存)、批量+压缩(省网络)------四个"反直觉"叠出百万 TPS。
- 零拷贝 :sendfile 内核态 DMA 传输------省 2 次 CPU 拷贝+2 次上下文切换;成立前提是 Broker 不加工消息(端到端压缩块原样存储)。
- PageCache 妙用 :写入内存速度、读取命中缓存、无 GC、重启不冷启动------Kafka 把缓存外包给 OS,比自己管聪明。
- 批量+压缩复利 :请求数 ÷100 × 传输量 ÷3 = 吞吐提升一个数量级------压缩块从 Producer 到 Consumer 只压一次解一次,Broker CPU 不参与。
- ZooKeeper 痛点:元数据扩展性差(Partition ~20 万上限)、Controller 切换全量加载(分钟级)、多运维一套集群。
- KRaft :元数据存内部 Topic(日志化)+ Controller Raft 组------去 ZooKeeper、秒级切换、Partition 百万级,3.3+ 生产可用。
📌 最后一句话 :Kafka 的设计哲学是"把能外包的都外包 "------缓存外包给 OS 的 PageCache(无 GC)、传输外包给内核的 sendfile(零拷贝)、顺序外包给磁盘的物理特性(顺序写快)、协调外包给 Raft(KRaft)。Kafka 自己只做最薄的一层:Partition 管理+副本同步+Offset 管理 ,其余全交给操作系统和硬件。这就是"少即是多"的架构智慧------做得越少,越快。
📌 配套阅读:
上一篇:《06-08-B-RocketMQ面试与生产事故实战.md》
如果这篇文章对你有帮助,欢迎点赞、收藏、关注!