Apache Kafka 指南

1 阅读5分钟

Apache Kafka 指南

前言:为什么你需要这份文档?

作为 Java 开发者,你可能已经会用 @KafkaListener,但当面对以下问题时是否依然困惑:

  • ❓ Kafka 为什么能扛住百万 TPS?底层存储如何设计?

  • ❓ Consumer 扩容后,为什么所有服务突然停止消费?

  • ❓ 如何实现“消费 → 处理 → 再生产”的端到端 Exactly-Once?

  • ❓ 线上积压百万消息,该看哪些监控指标?

一、Kafka 核心原理:不只是队列,而是高性能日志引擎

1、分区(Partition):并行处理的基石

graph TB
    T[Topic: user-events] --> P0[Partition 0]
    T --> P1[Partition 1]
    T --> P2[Partition 2]

    P0 --> S0["segment-0.log\noffset 0-999"]
    P0 --> S1["segment-1.log\noffset 1000-1999"]
    P1 --> S2["segment-0.log\noffset 0-888"]
  • 每个 Partition 是一个有序、不可变的消息日志

  • 日志被切分为多个 Segment 文件(默认 1GB)

  • Producer 通过 key 决定消息路由:

// 相同 key → 相同 Partition → 顺序保证
kafkaTemplate.send("topic", userId, event);

2、存储引擎:.log + .index 如何协同工作?

Kafka 内部有个计数器,每当新写入的消息使 Segment 的总大小增加 ≥ 4096 字节(4KB)时,就为这条消息建一条索引。

📍 什么是 position

position = 消息在 .log 文件中的物理字节偏移量(从文件开头算起)

📍核心思想:文件名 = Segment 的起始 offset

Kafka 为每个 Partition 创建多个 Segment 文件,文件名就是该 Segment 第一条消息的 offset。

Kafka 的高性能秘诀在于 .log 顺序 I/O + .index 稀疏索引:

graph LR
    A[请求 offset=150] --> B{定位 Segment}
    B --> C["在 .index 中<br/>二分查找"]
    C --> D["找到最近索引<br/>offset=100 → position=4096"]
    D --> E["从 .log 的<br/>position=4096 开始扫描"]
    E --> F["找到 offset=150 的消息"]

graph LR
    O[Offset 1500] --> I["Index File<br/>(每4KB一条)"]
    I --> L["Log File<br/>(Position 6144)"]
    L --> M[Message Content]

    subgraph Segment Files
        I
        L
    end

  • .log 文件:存储实际消息(顺序追加)

  • .index 文件:稀疏索引(offset → position 映射)

  • 查询流程:

    1. 二分查找 .index 定位最近 offset

    2. 顺序扫描 .log 找到目标消息

💡 优势:

  • 写入:纯顺序 I/O(磁盘最快模式)

  • 读取:O(log N) 定位 + 顺序扫描(避免随机读)

3、副本同步:ISR 机制保障高可用

graph LR
    P[Partition] --> L[Leader]
    P --> F1[Follower 1]
    P --> F2[Follower 2]

    L -->|同步| F1
    L -->|同步| F2

    style L fill:#b7eb8f
    style F1 fill:#ffe58f
    style F2 fill:#ffe58f
  • Leader:处理所有读写请求

  • Follower:从 Leader 拉取数据

  • ISR(In-Sync Replicas):与 Leader 保持同步的副本集合

⚠️ 关键配置:→ 配合 Producer acks=all,实现强一致性

二、消息有序性:原理、陷阱与生产方案

1、有序性的唯一保证:Partition 内有序

graph LR
    K1[key=userA] --> P0[Partition 0]
    K2[key=userB] --> P1[Partition 1]
    K3[key=userA] --> P0

    P0 --> M1["userA: login (offset 0)"]
    P0 --> M2["userA: logout (offset 1)"]
    P1 --> M3["userB: login (offset 0)"]

✅ 结论:

  • 相同 key → 相同 Partition → 严格有序

  • 不同 key → 可能不同 Partition → 全局无序

2、三大破坏顺序的陷阱与解决方案

陷阱 1:Producer 重试导致乱序
  • 场景:网络抖动,msg1 超时重试,msg2 先写入

  • 解决方案:启用幂等 Producer

spring:
  kafka:
    producer:
      enable-idempotence: true  # 自动分配 PID + 序列号
      retries: 2147483647       # 无限重试(安全)

Broker 为每个 (PID, Partition) 维护一个高水位序列号:

  • 如果收到的消息 seq <= 高水位 → 重复消息,丢弃

  • 如果 seq == 高水位 + 1 → 新消息,接受并更新高水位

sequenceDiagram
    participant P as Producer
    participant B as Broker

    P->>B: 发送 msg1 (PID=100, seq=1)
    B-->>P: 成功
    B->>B: 记录 (PID=100, seq=1)

    P->>B: 发送 msg2 (PID=100, seq=2) 
    Note right of P: 网络超时,未收到响应
    P->>B: 重试 msg2 (PID=100, seq=2)
    B->>B: seq=2 == 1+1 → 接受
    B-->>P: 成功

    P->>B: 发送 msg3 (PID=100, seq=3)
    B->>B: seq=3 == 2+1 → 接受

陷阱 2:多线程消费破坏顺序
  • 场景:异步线程池处理消息

  • 解决方案:单 Partition 单线程

spring:
  kafka:
    listener:
      concurrency: 1  # 每个实例 1 个消费线程
陷阱 3:Consumer Rebalance 导致重复消费
  • 场景:扩容 Consumer,触发 Rebalance

  • 解决方案:延长处理超时 + 减少单次负载

spring:
  kafka:
    consumer:
      max-poll-interval-ms: 300000  # 默认 5 分钟 → 延长至 5 分钟
    listener:
      max-poll-records: 50          # 默认 500 → 减少单次处理量

三、Consumer Rebalance:代价与规避(生产必读)

1、什么是 Rebalance?

当 Consumer Group 成员变化时,Kafka 会重新分配 Partition,期间所有消费者暂停消费(Stop The World)。

sequenceDiagram
    Consumer1->>Kafka: 加入 Group
    Kafka->>All Consumers: 触发 Rebalance
    All Consumers->>All Consumers: 停止消费
    Kafka->>Consumer1: 分配 Partition
    Kafka->>Consumer2: 重新分配 Partition
    All Consumers->>Kafka: 恢复消费

2、触发条件

  • 新 Consumer 加入或退出

  • 订阅的 Topic 变化

  • 单次 poll() 处理超时(max.poll.interval.ms

3、规避方案

  1. 避免频繁启停 Consumer

  2. 优化业务逻辑(确保在 max.poll.interval.ms 内完成)

  3. 调整参数:

spring:
  kafka:
    consumer:
      # 延长最大处理时间(单位:毫秒)
      max-poll-interval-ms: 300000
    listener:
      # 减少单次拉取消息数
      max-poll-records: 50

四、Exactly-Once 语义(EOS):端到端实现

1、场景:消费 → 处理 → 再生产

例如:
订单 Topic → 处理 → 发送到风控 Topic

2、实现方案:事务性消费

@Service
public class TransactionalConsumer {

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @KafkaListener(topics = "input-topic")
    public void listen(List<ConsumerRecord<String, String>> records) {
        // 在事务中执行:消费 + 处理 + 再生产
        kafkaTemplate.executeInTransaction(t -> {
            // 1. 处理业务
            List<String> outputs = process(records);
            // 2. 发送新消息(同一事务)
            outputs.forEach(msg -> t.send("output-topic", msg));
            // 3. 手动提交 offset(事务内)
            return true;
        });
    }

    private List<String> process(List<ConsumerRecord<String, String>> records) {
        // 业务逻辑
        return records.stream().map(r -> "processed: " + r.value()).collect(Collectors.toList());
    }
}

3、必须配置

spring:
  kafka:
    producer:
      transaction-id-prefix: tx-  # 启用事务
    consumer:
      # 只读已提交消息(避免脏读)
      properties:
        isolation.level: read_committed

✅ 效果:

  • 消息不会丢失

  • 消息不会重复

  • 消息不会乱序(同一 Partition 内)

五、生产监控:关键指标与告警阈值

1、必监控指标清单

指标说明告警阈值工具
kafka.consumer.lag消费延迟(未消费消息数)> 10,000Prometheus + Grafana
request.handler.avg.idle.pctBroker 繁忙度< 0.2JMX
under_replicated_partitions副本不同步分区数> 0Kafka Manager
record.error.rateProducer 发送失败率> 0.01Micrometer

2、Spring Boot 集成监控

management:
  endpoints:
    web:
      exposure:
        include: health,metrics
  metrics:
    tags:
      application: ${spring.application.name}

访问 http://localhost:8080/actuator/metrics 查看 Kafka 指标。


六、技术选型:Kafka vs 其他消息系统

1、适用场景对比

系统吞吐量延迟持久化适用场景
Kafka⭐⭐⭐⭐⭐ (10w+/s)毫秒级磁盘(高性能)日志聚合、流处理、事件溯源
RabbitMQ⭐⭐⭐ (万级)微秒级内存+磁盘任务队列、RPC、低延迟场景
RocketMQ⭐⭐⭐⭐ (10w+)毫秒级磁盘金融级事务、顺序消息
Pulsar⭐⭐⭐⭐毫秒级分层存储多租户、跨地域复制

2、何时不选 Kafka?

  • 需要点对点队列模型 → RabbitMQ

  • 消息需 TTL 自动过期 → RocketMQ

  • 强多租户隔离 → Pulsar

  • 超低延迟(<1ms) → RabbitMQ


七、生产 Checklist:Java 开发必备配置

1、Producer 配置

spring:
  kafka:
    producer:
      enable-idempotence: true
      acks: all
      retries: 2147483647
      batch-size: 16384
      linger-ms: 20
      compression-type: snappy

2、Consumer 配置

spring:
  kafka:
    consumer:
      enable-auto-commit: false
      max-poll-records: 50
      max-poll-interval-ms: 300000
      properties:
        isolation.level: read_committed
        spring.json.trusted.packages: com.yourcompany.model
    listener:
      ack-mode: manual_immediate
      concurrency: 3

3、业务代码规范

  1. Key 必须设:用业务 ID(如 user_id)

  2. 手动 ACK:先处理,再提交

  3. 幂等处理:数据库唯一索引 / 状态机

  4. 异常捕获:不提交 offset,允许重试


八、总结:Kafka 的核心哲学

  • 高性能:靠 顺序 I/O + 批量 + 压缩

  • 高可靠:靠 ISR + 幂等 + 手动 ACK

  • 高可用:靠 多副本 + 自动 Leader 选举

  • 易扩展:靠 Partition 水平拆分