Skip to content

分区副本与顺序性

1. 这是什么

分区决定并发和扩展能力,副本决定容灾和可靠性,顺序性则是业务经常关心的消费属性。
这三者是 Kafka 设计权衡的核心。

一句话理解:

  • 分区解决扩展
  • 副本解决容灾
  • 顺序性解决业务处理约束

2. 为什么重要

一旦业务同时要求:

  • 高吞吐
  • 高可靠
  • 严格顺序

你就必须理解这三者之间的关系。
否则系统设计很容易出现错误预期。

3. 先建立直觉:严格全局顺序和无限扩展通常不能同时轻松获得

这是学习 Kafka 时特别重要的一条直觉。

Kafka 最稳定能保证的顺序,通常是:

  • 单分区内顺序

而不是:

  • 整个 Topic 全局严格顺序

因为一旦你把数据拆到多个分区并行处理,就自然会引入:

  • 并行
  • 交错
  • 局部有序但全局不完全可比

4. 核心内容

4.1 分区扩展能力

分区是 Kafka 的扩展单元。
它的价值在于:

  • 让生产和消费可以并行

所以更多分区通常意味着:

  • 更强扩展潜力

但也会带来:

  • 更复杂的管理
  • 更复杂的顺序和再均衡问题

4.2 副本机制

副本的核心目标是:

  • 提升可靠性和容灾能力

一份分区数据通常不只存在一个副本上。
这让节点故障时:

  • 仍有机会从副本中继续服务

4.3 ISR 基本认知

学习阶段先抓住一个足够实用的理解:

  • ISR 是当前和 leader 保持足够同步的一组副本

它和写入确认、故障切换都高度相关。

4.4 分区内顺序

Kafka 最容易强保证的顺序是:

  • 单个分区内,按写入顺序追加并消费

这也是为什么业务分区键设计特别关键。

4.5 多分区下的顺序边界

多个分区同时存在时,通常只能说:

  • 每个分区内部有序

但很难说:

  • 所有分区全局完全严格有序

5. 学习重点

这一章最重要的是掌握:

  • 顺序性通常只能在单分区范围内强保证
  • 副本机制会影响写入确认与可用性
  • 分区越多,不等于所有问题都更好
  • 分区键设计直接影响顺序性体验

6. 常见问题

6.1 既想全局严格顺序又想无限扩展

这是非常典型的架构幻想。

6.2 不理解副本同步延迟带来的影响

副本不是“只要有就行”,同步状态也很重要。

6.3 分区键设计不稳定导致顺序失真

如果业务 key 没设计好,顺序和聚合语义都可能被破坏。

7. 动手验证

当前环境没有 Kafka CLI,这里用纯 Java demo 验证“同 key 同分区有序、不同分区全局不保证顺序”。

7.1 准备一个可运行示例

新建文件 KafkaOrderingDemo.java,内容如下:

java
import java.util.ArrayList;
import java.util.List;

public class KafkaOrderingDemo {
    static class Event {
        final String orderId;
        final String status;
        final int partition;

        Event(String orderId, String status, int partition) {
            this.orderId = orderId;
            this.status = status;
            this.partition = partition;
        }
    }

    public static void main(String[] args) {
        int partitions = 2;
        List<List<Event>> topic = new ArrayList<>();
        for (int i = 0; i < partitions; i++) {
            topic.add(new ArrayList<>());
        }

        append(topic, "order-1", "created");
        append(topic, "order-2", "created");
        append(topic, "order-1", "paid");
        append(topic, "order-2", "paid");

        for (int p = 0; p < topic.size(); p++) {
            System.out.println("partition=" + p);
            for (Event event : topic.get(p)) {
                System.out.println(event.orderId + "-" + event.status);
            }
        }
    }

    private static void append(List<List<Event>> topic, String orderId, String status) {
        int partition = Math.abs(orderId.hashCode()) % topic.size();
        topic.get(partition).add(new Event(orderId, status, partition));
    }
}

7.2 编译并运行

bash
javac KafkaOrderingDemo.java
java KafkaOrderingDemo

7.3 你应该观察到什么

输出不一定完全一致,但应包含这些现象:

  • 同一个 orderId 的事件会落到同一个分区
  • 在该分区内,它们顺序保持 created -> paid
  • 不同分区之间不存在天然全局顺序

8. 练习建议

  • 为一个订单场景设计分区键
  • 说明为什么单分区更容易保证顺序
  • 总结分区与顺序性之间的权衡
  • 用自己的话解释副本和顺序为什么是两个不同问题

9. 自测问题

  • Kafka 的顺序性通常能保证到什么粒度?
  • 分区和副本分别更偏向解决什么问题?
  • 为什么业务分区键设计很关键?
  • 为什么全局严格顺序会限制扩展性?

10. 自测核对要点

  • 分区主要解决扩展和并行
  • 副本主要解决可靠性和容灾
  • Kafka 强顺序通常是单分区级别
  • 分区键设计直接影响业务顺序体验