乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 消息队列与 Kafka 概述

    • 章节导读
    • 消息队列基础
    • Kafka 概述
    • Kafka 为什么快
  • 第2章 快速上手:单机环境搭建

    • 章节导读
    • 环境准备与安装
    • 快速启动
    • Topic 管理
  • 第3章 核心概念:主题、分区与日志

    • 章节导读
    • Topic
    • Partition
    • Offset
    • Segment 与存储结构
  • 第4章 生产者详解

    • 章节导读
    • Producer 概述
    • Producer 发送机制
    • Producer 分区策略
    • Producer 幂等与事务
    • Producer 配置
  • 第5章 消费者与消费组

    • 章节导读
    • Consumer 概述
    • Consumer Group 消费组
    • Consumer 分区分配策略
    • Consumer Offset 提交
    • Consumer 多线程
    • Consumer 配置
  • 第6章 Broker 与控制器

    • 章节导读
    • Broker 概述
    • Broker 配置
    • Controller
    • KRaft 共识协议
  • 第7章 副本与数据可靠性

    • 章节导读
    • 副本机制
    • ISR 与副本同步
    • Leader 选举
    • ACK 与一致性保证
    • 高水位与 Leader Epoch
  • 第8章 存储与性能优化

    • 章节导读
    • 存储架构
    • 日志清理与压缩
    • Page Cache 与零拷贝
    • Producer 性能优化
    • Consumer 性能优化
    • Broker 性能优化
  • 第9章 生产环境运维与监控

    • 章节导读
    • Topic 管理
    • Kafka 运维工具
    • 监控
    • 常见故障排查
  • 第10章 Kafka生态与面试考点

    • 章节导读
    • Kafka 生态全景
    • 面试高频 30 题

Segment 与存储结构

定义与作用

Segment(日志段)是 Kafka 在磁盘上存储消息的物理单元。每个 Partition 在磁盘上对应一个目录,目录内包含多个 Segment 文件。Segment 解决了"无限追加的日志文件"的工程瓶颈——若不切分,单个文件会无限增长,导致文件系统难以管理、清理操作无法高效执行。

Segment 之于 Partition,如同 logrotate 之于 syslog:将连续的日志流切分为固定大小的物理文件,便于管理和清理。

核心原理

Partition 的磁盘布局

每个 Segment 由三种文件组成:

文件类型命名规则内容
.log以起始 Offset 命名(20位数字左补零)消息数据
.index同前缀Offset → 物理位置的稀疏索引
.timeindex同前缀时间戳 → Offset 的映射

当前正在写入的 Segment 称为 Active Segment,只有它接受写入。当 Active Segment 大小达到 log.segment.bytes(默认 1GB)或时间达到 log.roll.ms(默认 7 天),Kafka 会关闭旧 Segment,创建新的 Active Segment。

索引文件的工作原理

索引是稀疏的(默认每 4KB 写入一条索引),而非为每条消息建索引。查找 Offset=55 的消息时:

  1. 在 .index 中找到 ≤55 的最大索引项(Offset=50, 位置=4872)
  2. 从日志文件的位置 4872 开始顺序扫描,直到找到 Offset=55

完整示例

示例一:直接观察磁盘上的 Segment 文件

操作前环境:Topic segment-demo 已创建(1 分区),持续写入消息。

步骤:

# 1. 查看 Partition 存储目录
ls -lh /tmp/kraft-combined-logs/segment-demo-0/
# total 32M
# 00000000000000000000.index    10485760
# 00000000000000000000.log      20971520    ← Active Segment
# 00000000000000000000.timeindex 10485760
# leader-epoch-checkpoint

# 2. 修改 log.segment.bytes 为较小值(仅用于演示观察)
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --entity-type topics --entity-name segment-demo \
  --alter --add-config segment.bytes=1048576
# (1MB, 需要重启或等待 Segment Roll)

# 3. 使用 kafka-dump-log.sh 查看 Segment 内容
bin/kafka-dump-log.sh \
  --files /tmp/kraft-combined-logs/segment-demo-0/00000000000000000000.log \
  --print-data-log
# baseOffset: 0 lastOffset: 0 ...
# baseOffset: 1 lastOffset: 1 ...

操作后对比:

时机文件列表
Segment Roll 前00000000000000000000.log(一个文件,持续增长)
Segment Roll 后00000000000000000000.log(只读)+ 00000000000000000150.log(Active Segment,150 为新起始 Offset)

示例二:时间戳索引的实际应用

场景:需要找到 2024 年 6 月 1 日 10:30 之后的所有消息,Consumer 应从哪个 Offset 开始消费。

步骤:

# 1. 将时间戳转换为 Unix 毫秒
date -d "2024-06-01 10:30:00" +%s000
# 1717235400000

# 2. 使用 GetOffsetShell 按时间戳查找 Offset
bin/kafka-run-class.sh kafka.tools.GetOffsetShell \
  --topic segment-demo \
  --time 1717235400000 \
  --bootstrap-server localhost:9092
# segment-demo:0:15234

# 3. 时间索引查找过程(内部机制,无需手动执行):
#    .timeindex → 找到 ≤ 1717235400000 的最大时间戳对应的 Offset
#    .index → 找到该 Offset 在 .log 中的物理位置
#    .log → 顺序扫描到精确位置

操作前后对比:

方式结果查找方式
按时间Offset=15234时间戳索引(O(1) 二分查找 + 少量顺序扫描)
无索引需要从头扫描 15K+ 条消息O(n) 全表扫描

消息格式(Record Batch)

Kafka 在磁盘上存储的单元不是单条消息,而是 Record Batch(消息批次):

Record Batch 结构:
┌─────────────────────┐
│ Base Offset (8B)    │  ← 批次中第一条消息的 Offset
│ Batch Length (4B)   │  ← 批次总字节数
│ Partition Leader... │
│ Magic (1B)          │  ← v2 消息格式
│ CRC (4B)            │
│ Attributes (2B)     │  ← 压缩类型、时间戳类型
│ Last Offset Delta   │
│ Base Timestamp (8B) │
│ Max Timestamp (8B)  │
│ Producer ID (8B)    │  ← 幂等生产者
│ Producer Epoch (2B) │
│ Base Sequence (4B)  │
│ Records Count (4B)  │
├─────────────────────┤
│ Record 1            │
│ Record 2            │
│ ...                 │
└─────────────────────┘

关键要点:Producer 压缩的是 Record Batch 层级,而非单条消息层级。这就是"端到端压缩"能获得高压缩比的原因。

易错场景

易错 1:log.segment.bytes 设置过大

场景:将 log.segment.bytes 设为 10GB,以为能减少文件数。

后果:

  1. Segment 文件过大,日志清理(Log Cleaner)无法及时删除过期数据
  2. 崩溃恢复时需重建过大的 Segment 索引,启动时间变长
  3. 无法利用 Segment 粒度的清理策略

建议:保持默认 1GB,或在 512MB-2GB 之间调整。

易错 2:忘记 log.dirs 可以配置多目录

场景:单 Broker 有 4 块 SSD,但只配置了 log.dirs=/data1/kafka,其他三块磁盘闲置。

正确做法:log.dirs=/data1/kafka,/data2/kafka,/data3/kafka,/data4/kafka,Kafka 会将不同 Partition 的 Segment 分布到不同磁盘上,实现磁盘级别的并行 I/O。

面试高频考点

Q:为什么 Kafka 的索引是稀疏的而不是密集的?

A:

  1. 内存效率:密集索引(每条消息一个条目)在 TB 级数据下会消耗大量内存。稀疏索引将索引大小控制在数据量的约 1%
  2. 顺序 I/O 的优势:找到索引位置后,顺序扫描一段数据(最多 4KB)的开销极低,不需要精确定位到每条消息
  3. 设计哲学:Kafka 始终优先选择"顺序扫描 + 小内存"而非"精确随机访问 + 大内存",这与 Page Cache 和顺序 I/O 的设计一脉相承
上一页
Offset