Kafka 架构与分区

某团队在使用 Kafka 时,遇到了消息顺序错乱的问题。

场景:用户下单后,系统发送了三条消息:创建订单 → 扣库存 → 扣余额。消费者收到的顺序变成了:扣库存 → 创建订单 → 扣余额,导致业务逻辑错乱。

排查后发现:消息被分散到了不同的分区,而消费者使用了多线程并发消费,不同分区的消息处理顺序不受 Kafka 保证。

这就是 Kafka 分区机制带来的"顺序性问题"。

【架构权衡】 Kafka 的分区机制是实现高吞吐的关键,但同时也带来了"分区内的有序性 vs 分区间的无序性"问题。理解分区的设计,才能正确使用 Kafka。


一、核心问题 🔴

1.1 Kafka 核心架构

┌─────────────────────────────────────────────────────────────────┐
│                        Kafka 核心架构                             │
│                                                                  │
│  Producer ──► Topic ──► Partition ──► Leader ──► Consumer       │
│                 │           │           │                        │
│                 │           │           └──► Follower           │
│                 │           │                                    │
│                 │           └──► Partition Replica              │
│                 │                                            │
│                 └──► Partition Log                            │
└─────────────────────────────────────────────────────────────────┘

核心概念:
├─ Broker:Kafka 服务器节点
├─ Topic:消息主题
├─ Partition:Topic 的分区(并行度)
├─ Replica:分区的副本(高可用)
├─ Leader:分区的主副本(读写)
└─ Follower:从 Leader 同步数据

1.2 分区机制

分区的核心作用:

1. 并行处理
   └─ Partition 是并行度单位
   └─ N 个 Partition → 最多 N 个并发消费者

2. 负载均衡
   └─ 消息分散到不同 Partition
   └─ 不同 Broker 处理不同 Partition

3. 顺序保证
   └─ 单 Partition 内有序
   └─ 多 Partition 间无序

分区策略:
├─ DefaultPartitioner:基于 key 的 hash
├─ RoundRobinPartitioner:无 key 时轮询
└─ 自定义分区策略

1.3 副本机制与 ISR

ISR(In-Sync Replicas):

ISR 包含:
├─ Leader(始终在 ISR 中)
└─ 跟上 Leader 的 Follower

Follower 同步条件:
├─ 拉取间隔 < replica.lag.time.max.ms
└─ 拉取到的消息 >= Leader 的最新消息

Leader 选举:
├─ 优先从 ISR 中选举
├─ ISR 为空?从 OSR 中选举
└─ OSR 也为空?选举失败

配置:
replication.factor=3      # 副本数
min.insync.replicas=2    # ISR 最少副本数

二、生产避坑

2.1 顺序性问题

// 问题:多 Partition + 多 Consumer 线程导致乱序
// 解决1:使用单 Partition
kafkaTemplate.send("order-topic", orderId, message);

// 解决2:使用 key 保证同一 key 的消息在同一 Partition
// 基于 key 的 hash,同一个订单的所有消息都在同一 Partition
kafkaTemplate.send("order-topic", orderId, message);

// 解决3:使用单 Consumer 线程
@KafkaListener(topics = "order-topic", concurrency = "1")

2.2 消息丢失问题

// 生产端:确认机制
kafkaTemplate.send("topic", message)
    .get(10, TimeUnit.SECONDS); // 同步等待确认

// Broker 端:副本确认
// acks=all:所有 ISR 确认后才返回
// acks=1:Leader 确认后返回
// acks=0:不等待

// 配置建议
spring:
  kafka:
    producer:
      acks: all           # 所有副本确认
      retries: 3          # 重试次数
      enable-idempotence: true  # 幂等生产者

三、落地 Checklist

  • 分区规划:评估分区数量(太少并发低,太多元数据多)
  • 副本规划:生产环境至少 3 副本
  • ISR 配置:min.insync.replicas >= 2
  • 顺序性设计:需要有序时使用 key
  • 监控部署:监控 Lag、ISR 变化