🎯 Kafka的分区策略:让消息找到回家的路!

40 阅读16分钟

📚 前言:快递小哥的困惑

想象一下,你是一个快递站的站长 👨‍💼,每天有成千上万的包裹需要分配给不同的快递小哥。你会怎么分?

  • 随机扔?❌ 太混乱了!
  • 按照地址分?✅ 这样同一个小区的包裹都给同一个快递员,效率高!
  • 轮流分?✅ 确保每个快递员工作量均衡!

这就是Kafka的分区策略要解决的问题! 🎊


🤔 什么是Kafka的分区?

基础概念

在Kafka中:

  • Topic(主题) = 一个大仓库 🏭
  • Partition(分区) = 仓库里的多个货架 📦📦📦
  • Message(消息) = 要存放的货物 📨

为什么要分区?🤔

  1. 并行处理 - 多个消费者可以同时从不同分区读取,就像多个收银员同时服务顾客 💰
  2. 扩展性 - 单个分区存不下了?加分区!就像超市货架不够加货架 🛒
  3. 容错性 - 一个分区坏了,其他分区还能用 💪

🎲 生产者的分区策略:消息该去哪儿?

1️⃣ 指定分区(Explicit Partition)

最直接粗暴的方式!

ProducerRecord<String, String> record = 
    new ProducerRecord<>(
        "my-topic",     // Topic
        2,              // 指定分区编号:分区2
        "key",          // Key
        "value"         // Value
    );

生活比喻:📮 你在快递单上直接写:"送到3号货架"。快递员不用思考,直接送到3号货架!

适用场景

  • 你知道某些消息必须去特定分区
  • 测试场景
  • 特殊业务需求

2️⃣ 根据Key分区(Key-based Partitioning)

最常用的策略! ⭐⭐⭐⭐⭐

ProducerRecord<String, String> record = 
    new ProducerRecord<>(
        "my-topic",
        "user-123",     // Key
        "订单数据"       // Value
    );

工作原理

分区编号 = hash(key) % 分区总数

生活比喻:🏠 按照用户的地址(Key)分配快递员。住在"阳光小区"的所有包裹,永远是同一个快递员配送!

核心特点

  • 相同Key的消息,一定去同一个分区
  • 保证顺序性(同一个用户的消息顺序不会乱)
  • 负载均衡(不同Key会分散到不同分区)

示例场景

// 用户123的所有订单消息都会在同一个分区
send("user-123", "创建订单");
send("user-123", "支付订单");
send("user-123", "发货");
// 这三条消息的顺序被保证! 🎯

图示

Key: "user-A"hash → 123456 → 123456 % 3 = 0 → Partition 0 
Key: "user-B"hash → 789012 → 789012 % 3 = 1 → Partition 1
Key: "user-C"hash → 345678 → 345678 % 3 = 2 → Partition 2
Key: "user-A"hash → 123456 → 123456 % 3 = 0 → Partition 0 (又是分区0!)

3️⃣ 轮询分区(Round-Robin)

没有Key时的默认策略!

ProducerRecord<String, String> record = 
    new ProducerRecord<>(
        "my-topic",
        null,           // Key为null
        "消息内容"
    );

工作原理

1条消息 → Partition 02条消息 → Partition 13条消息 → Partition 24条消息 → Partition 0  (又回到0,循环!)

生活比喻:🎰 像发扑克牌一样,一人一张,轮流发!确保每个人手里的牌数量差不多。

特点

  • ✅ 负载均衡,每个分区消息数量大致相等
  • ❌ 无法保证顺序性
  • ⚡ 吞吐量较高

4️⃣ 自定义分区器(Custom Partitioner)

终极大招:我的规则我做主! 🎨

public class VIPPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                        Object value, byte[] valueBytes, Cluster cluster) {
        
        String keyStr = (String) key;
        int numPartitions = cluster.partitionCountForTopic(topic);
        
        // VIP用户走专属分区!
        if (keyStr.startsWith("VIP-")) {
            return 0; // 分区0专门给VIP
        }
        
        // 普通用户走其他分区
        return (Math.abs(keyStr.hashCode()) % (numPartitions - 1)) + 1;
    }
}

生活比喻:🎭 银行的VIP客户有专属窗口,普通客户在其他窗口排队!


🎯 分区策略对比表

策略Key顺序性负载均衡使用场景
指定分区不需要测试、特殊需求
Key分区必须✅(同Key)最常用,需要顺序性
轮询null无顺序要求,追求吞吐
自定义可选自定义自定义特殊业务逻辑

👥 消费者组的负载均衡:谁来消费哪个分区?

什么是消费者组?

消费者组(Consumer Group) = 一个团队 🤝

  • 团队里的每个成员(Consumer)负责处理一部分分区
  • 同一条消息,只会被团队里的一个成员处理
  • 不同团队可以重复消费相同的消息

图示

Topic: orders (3个分区)
    ├─ Partition 0 📦
    ├─ Partition 1 📦
    └─ Partition 2 📦

Consumer Group A: 订单处理团队
    ├─ Consumer A1 → Partition 0 ✅
    ├─ Consumer A2 → Partition 1 ✅
    └─ Consumer A3 → Partition 2 ✅

Consumer Group B: 数据分析团队
    ├─ Consumer B1 → Partition 0, 1 ✅
    └─ Consumer B2 → Partition 2

🎪 消费者分区分配策略

1️⃣ Range分配策略(RangeAssignor)

按范围分配,简单粗暴!

工作原理

  1. 把分区按顺序排列
  2. 把消费者按字母顺序排列
  3. 分区数 ÷ 消费者数 = 每人几个
  4. 余数分给前面的消费者

示例

Topic: orders,有7个分区 (P0-P6)
Consumer Group: 3个消费者 (C1, C2, C3)

7 ÷ 3 = 21

分配结果:
C1: [P0, P1, P2]  ← 多分配1个(因为余数是1)
C2: [P3, P4]
C3: [P5, P6]

生活比喻:🍰 切蛋糕,尽量平均分,分不匀的话,老大多吃一块!

优点

  • ✅ 简单易懂
  • ✅ 实现简单

缺点

  • 不够均衡! 第一个消费者可能分到更多分区
  • ❌ 多个Topic时,不均衡问题会放大

多Topic场景问题

Topic1: 3个分区   Topic2: 3个分区
C1: T1[P0], T2[P0] = 2个分区
C2: T1[P1], T2[P1] = 2个分区  
C3: T1[P2], T2[P2] = 2个分区

看起来均衡?但如果是:

Topic1: 4个分区   Topic2: 4个分区
C1: T1[P0,P1], T2[P0,P1] = 4个分区 😱
C2: T1[P2], T2[P2] = 2个分区
C3: T1[P3], T2[P3] = 2个分区

2️⃣ RoundRobin分配策略(RoundRobinAssignor)

轮流分配,更均衡!

工作原理

  1. 把所有Topic的所有分区放一起
  2. 排个序
  3. 像发扑克牌一样,一人一个,轮着来!

示例

Topic: orders,7个分区 (P0-P6)
Consumer Group: 3个消费者 (C1, C2, C3)

分配过程:
P0 → C1
P1 → C2
P2 → C3
P3 → C1  (回到C1)
P4 → C2
P5 → C3
P6 → C1

最终分配:
C1: [P0, P3, P6]  ← 3C2: [P1, P4]      ← 2C3: [P2, P5]      ← 2

生活比喻:🎴 真的像发扑克牌!你一张,我一张,他一张...

优点

  • ✅ 比Range更均衡
  • ✅ 多Topic时也能保持均衡

缺点

  • ❌ 如果消费者订阅的Topic不同,可能出现分配不均

3️⃣ Sticky分配策略(StickyAssignor)⭐⭐⭐⭐⭐

最智能的策略:既均衡又稳定!

设计目标

  1. 尽量均衡:和RoundRobin一样均衡
  2. 尽量粘性:Rebalance时,尽量保持之前的分配方案

为什么需要粘性?

想象一下:

初始分配:
C1: [P0, P1]
C2: [P2, P3]
C3: [P4, P5]

C3挂了,需要Rebalance!

如果用RoundRobin:
C1: [P0, P2, P4]  ← P1没了,多了P2、P4
C2: [P1, P3, P5]  ← P2没了,多了P1、P5
结果:所有消费者的分区都变了!😱

如果用Sticky:
C1: [P0, P1, P4]  ← 保留P0、P1,只加P4
C2: [P2, P3, P5]  ← 保留P2、P3,只加P5
结果:只改变最少的分区分配!✅

生活比喻:🪑 公司重新分座位,尽量让大家坐在原来的位置附近,而不是全部重新安排!

优点

  • ✅ 分配均衡
  • ✅ Rebalance时减少分区迁移
  • ✅ 减少状态丢失和重复消费

这是Kafka 2.4+的默认策略! 🎉


4️⃣ CooperativeSticky(协作式粘性)

Sticky的升级版!

区别

  • 传统Sticky:Rebalance时,所有消费者都停止消费 😴
  • CooperativeSticky:Rebalance时,只停止需要迁移的分区 😎

图示

C3挂了,P4和P5需要重新分配

传统方式:
C1: 停止消费 → 等待分配 → 重新开始
C2: 停止消费 → 等待分配 → 重新开始

CooperativeSticky:
C1: P0、P1继续消费,只有新加入的P4暂停一下
C2: P2、P3继续消费,只有新加入的P5暂停一下

生活比喻:🚗 修路时,不是封闭整条路,而是只封闭要修的那一段,其他地方正常通行!


🔄 Rebalance:消费者组的重新洗牌

什么是Rebalance?

Rebalance(再均衡) = 重新分配分区的过程 🔄

触发条件

  1. 新消费者加入 👋

    之前:C1、C2
    之后:C1、C2、C3(新来的!)
    需要重新分配!
    
  2. 消费者离开 👋

    之前:C1、C2、C3
    C2挂了!💀
    需要重新分配!
    
  3. 消费者无响应 😴

    C2处理太慢,心跳超时
    Kafka以为它挂了
    触发Rebalance!
    
  4. 订阅的Topic分区数变化 📊

    Topic从3个分区扩展到5个分区
    需要重新分配!
    
  5. 正则订阅时,匹配到新Topic 🎯

    订阅模式:订阅所有以"order-"开头的Topic
    新建了一个Topic: "order-vip"
    触发Rebalance!
    

Rebalance的问题 😰

Rebalance很贵!

  1. Stop The World 🛑

    • 所有消费者停止消费
    • 业务处理暂停
    • 吞吐量降为0
  2. 状态丢失 💔

    • 本地缓存失效
    • 重新加载数据
  3. 重复消费 🔁

    • offset可能还没提交
    • 重新消费已处理的消息

生活比喻:🎬 电影院重新分配座位,所有人都要站起来,电影暂停,等重新分配完才能继续看!


如何减少Rebalance?💡

1. 调大心跳超时时间

# 心跳间隔
heartbeat.interval.ms=3000

# 会话超时(心跳超时)
session.timeout.ms=30000  # 默认10秒,可以调大到30秒

# 消费超时
max.poll.interval.ms=300000  # 5分钟

解释

  • 消费者定期发送心跳:"我还活着!" ❤️
  • 如果超过session.timeout.ms没心跳,认为它挂了
  • 如果超过max.poll.interval.ms没有poll消息,也认为它挂了

2. 减少单次拉取的消息数量

# 单次拉取最大消息数
max.poll.records=100  # 默认500,可以调小

原因

  • 如果一次拉取太多消息,处理时间过长
  • 超过max.poll.interval.ms,触发Rebalance
  • 调小后,处理更快,不会超时

3. 提高消费速度 ⚡

// 异步处理
consumer.poll(Duration.ofMillis(100)).forEach(record -> {
    executor.submit(() -> {
        processMessage(record);
    });
});

4. 固定消费者数量

  • 避免频繁加入/离开
  • 使用容器编排时,固定副本数

5. 使用CooperativeSticky策略 🎯

partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor

🎓 知识总结卡片

生产者分区策略

┌─────────────────────────────────────┐
│  指定分区:直接指定,精确控制         │
│  Key分区:相同Key进同一分区(常用)   │
│  轮询:无Key时轮流分配,最均衡        │
│  自定义:自己的地盘自己做主          │
└─────────────────────────────────────┘

消费者分配策略

┌─────────────────────────────────────┐
│  Range:按范围分,简单但不均衡        │
│  RoundRobin:轮流分,更均衡          │
│  Sticky:均衡+粘性,减少迁移(推荐)  │
│  CooperativeSticky:增量再均衡(更好)│
└─────────────────────────────────────┘

Rebalance触发条件

┌─────────────────────────────────────┐
│  ✅ 消费者加入                      │
│  ✅ 消费者离开                      │
│  ✅ 消费者心跳超时                  │
│  ✅ 分区数变化                      │
│  ✅ 正则订阅匹配到新Topic           │
└─────────────────────────────────────┘

🎯 实战代码示例

生产者:使用Key分区

import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class KeyBasedProducer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", 
            "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", 
            "org.apache.kafka.common.serialization.StringSerializer");
        
        KafkaProducer<String, String> producer = new KafkaProducer<>(props);
        
        // 相同用户的消息会进入同一个分区!
        for (int i = 0; i < 10; i++) {
            String userId = "user-" + (i % 3); // 3个用户
            String message = "Message-" + i;
            
            ProducerRecord<String, String> record = 
                new ProducerRecord<>(
                    "orders",     // Topic
                    userId,       // Key(重要!)
                    message       // Value
                );
            
            producer.send(record, (metadata, exception) -> {
                if (exception == null) {
                    System.out.printf("用户[%s]的消息发送到分区[%d],offset[%d]%n",
                        userId, metadata.partition(), metadata.offset());
                }
            });
        }
        
        producer.close();
    }
}

/* 输出示例:
用户[user-0]的消息发送到分区[0],offset[0]
用户[user-1]的消息发送到分区[1],offset[0]
用户[user-2]的消息发送到分区[2],offset[0]
用户[user-0]的消息发送到分区[0],offset[1]  ← 又是分区0!
用户[user-1]的消息发送到分区[1],offset[1]  ← 又是分区1!
...
*/

消费者:配置Sticky策略

import org.apache.kafka.clients.consumer.*;
import java.util.*;
import java.time.Duration;

public class StickyConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "order-processing-group");
        props.put("key.deserializer", 
            "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", 
            "org.apache.kafka.common.serialization.StringDeserializer");
        
        // ⭐ 配置Sticky分配策略
        props.put("partition.assignment.strategy", 
            "org.apache.kafka.clients.consumer.StickyAssignor");
        
        // ⭐ 调优参数,减少Rebalance
        props.put("session.timeout.ms", "30000");         // 30秒
        props.put("heartbeat.interval.ms", "3000");       // 3秒
        props.put("max.poll.interval.ms", "300000");      // 5分钟
        props.put("max.poll.records", "100");             // 单次最多100条
        
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("orders"));
        
        // 监听Rebalance事件
        consumer.subscribe(Arrays.asList("orders"), new ConsumerRebalanceListener() {
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
                System.out.println("⚠️ Rebalance开始,失去分区: " + partitions);
                // 提交offset,避免重复消费
                consumer.commitSync();
            }
            
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
                System.out.println("✅ Rebalance完成,分配到分区: " + partitions);
            }
        });
        
        try {
            while (true) {
                ConsumerRecords<String, String> records = 
                    consumer.poll(Duration.ofMillis(100));
                
                for (ConsumerRecord<String, String> record : records) {
                    System.out.printf("消费消息 - 分区:%d, Offset:%d, Key:%s, Value:%s%n",
                        record.partition(), record.offset(), 
                        record.key(), record.value());
                    
                    // 业务处理...
                    processMessage(record);
                }
                
                // 手动提交offset
                consumer.commitAsync();
            }
        } finally {
            consumer.close();
        }
    }
    
    private static void processMessage(ConsumerRecord<String, String> record) {
        // 业务逻辑处理
        try {
            Thread.sleep(100); // 模拟处理时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

自定义分区器:VIP专属通道

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;

/**
 * 自定义分区器:VIP用户走专属分区
 * 
 * 业务需求:
 * - VIP用户的消息走分区0(专属快速通道)
 * - 普通用户的消息均匀分布在其他分区
 */
public class VIPPartitioner implements Partitioner {
    
    private static final int VIP_PARTITION = 0;
    
    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                        Object value, byte[] valueBytes, Cluster cluster) {
        
        // 获取该Topic的分区数量
        int numPartitions = cluster.partitionCountForTopic(topic);
        
        if (numPartitions == 1) {
            return 0; // 只有1个分区,直接返回
        }
        
        // Key为null,随机分配到非VIP分区
        if (key == null) {
            return (int) (Math.random() * (numPartitions - 1)) + 1;
        }
        
        String keyStr = key.toString();
        
        // VIP用户走专属分区0 🎩
        if (keyStr.startsWith("VIP-") || keyStr.startsWith("vip-")) {
            System.out.println("🎩 VIP用户 [" + keyStr + "] → VIP专属分区");
            return VIP_PARTITION;
        }
        
        // 普通用户走其他分区(1到n-1)
        int partition = (Math.abs(keyStr.hashCode()) % (numPartitions - 1)) + 1;
        System.out.println("👤 普通用户 [" + keyStr + "] → 分区" + partition);
        return partition;
    }
    
    @Override
    public void close() {
        // 清理资源(如果有的话)
    }
    
    @Override
    public void configure(Map<String, ?> configs) {
        // 初始化配置(如果需要的话)
    }
}

// 使用自定义分区器
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

// ⭐ 指定自定义分区器
props.put("partitioner.class", "com.example.VIPPartitioner");

KafkaProducer<String, String> producer = new KafkaProducer<>(props);

// 测试
producer.send(new ProducerRecord<>("orders", "VIP-001", "VIP订单"));
producer.send(new ProducerRecord<>("orders", "user-123", "普通订单"));

🎭 常见面试题速答

Q1: 为什么要有分区?

A: 三个原因!

  1. 并行处理 - 多个消费者同时工作,提高吞吐量 🚀
  2. 水平扩展 - 单机存不下,加机器加分区 💪
  3. 容错能力 - 分区有副本,一个挂了还有备份 🛡️

Q2: 如何保证消息的顺序性?

A:

  1. 使用相同的Key - 相同Key的消息会进入同一个分区,分区内有序
  2. 单分区 - 整个Topic只有1个分区(不推荐,性能差)
  3. 单消费者 - 一个分区只能被一个消费者消费,自然有序

示例

// 订单消息,用订单ID作为Key
producer.send(new ProducerRecord<>("orders", orderId, orderData));
// 同一个订单的所有操作,顺序保证!

Q3: 分区数量如何设置?

A: 经验公式!

分区数 = max(
    期望吞吐量 / 单个生产者吞吐量,
    期望吞吐量 / 单个消费者吞吐量
)

举例

  • 期望吞吐量:100 MB/s
  • 单个生产者:10 MB/s
  • 单个消费者:20 MB/s
分区数 = max(100/10, 100/20) = max(10, 5) = 10个分区

注意

  • ⚠️ 分区越多,Rebalance越慢
  • ⚠️ 分区越多,Leader选举越慢
  • ⚠️ 建议:单个Topic不超过100个分区

Q4: 消费者数量和分区数量的关系?

A:

消费者数 > 分区数 → 有消费者空闲(浪费资源)❌
消费者数 = 分区数 → 完美!每人一个分区 ✅
消费者数 < 分区数 → 一个消费者处理多个分区 ✅

最佳实践

  • 消费者数量 ≤ 分区数量
  • 推荐:消费者数 = 分区数(充分并行)

图示

3个分区,4个消费者:
P0 → C1
P1 → C2
P2 → C3
      C4 (闲置,浪费!)3个分区,3个消费者:
P0 → C1
P1 → C2
P2 → C3 ✅ 完美!

3个分区,2个消费者:
P0 → C1
P1, P2 → C2 ✅ 也可以!

Q5: Rebalance有多慢?

A:

  • 小集群(几个消费者):几百毫秒
  • 大集群(几十个消费者):几秒到几十秒
  • 超大集群(上百个):可能1分钟+ 😱

优化方法

  1. 使用CooperativeSticky策略
  2. 减少消费者的加入/离开频率
  3. 调优心跳和会话超时参数

Q6: 为什么推荐Sticky策略?

A: 举个例子!

场景:3个消费者,6个分区

初始分配

C1: [P0, P1]
C2: [P2, P3]
C3: [P4, P5]

C3挂了!

RoundRobin重新分配

C1: [P0, P2, P4]  ← 变化:失去P1,得到P2、P4
C2: [P1, P3, P5]  ← 变化:失去P2,得到P1、P5

变化率:4个分区迁移 / 6个分区 = 66% 😰

Sticky重新分配

C1: [P0, P1, P4]  ← 变化:只加P4
C2: [P2, P3, P5]  ← 变化:只加P5

变化率:2个分区迁移 / 6个分区 = 33% 😊

好处

  • ✅ 减少状态迁移
  • ✅ 减少缓存失效
  • ✅ 减少重复消费

📝 调优检查清单

生产者端 ✅

□ 合理使用Key,保证相同业务Key进同一分区
□ 分区数设置合理(考虑吞吐量)
□ 自定义分区器(如果有特殊需求)
□ 设置合适的acks参数(可靠性vs性能)
□ 启用压缩(snappy/lz4)

消费者端 ✅

□ 消费者数 ≤ 分区数
□ 使用Sticky或CooperativeSticky策略
□ session.timeout.ms = 30000(避免心跳超时)
□ max.poll.interval.ms = 300000(避免消费超时)
□ max.poll.records = 100-500(根据业务调整)
□ 手动提交offset(更可控)
□ 监听Rebalance事件,及时处理

监控指标 📊

□ records-lag-max(消费延迟)
□ rebalance-latency-avg(Rebalance耗时)
□ records-consumed-rate(消费速率)
□ fetch-latency-avg(拉取延迟)
□ commit-latency-avg(提交延迟)

🎬 总结:一张图看懂所有!

                      Kafka分区策略全景图
                             
┌─────────────────────────────────────────────────────────────┐
│                       生产者端                              │
│                                                             │
│  消息 → 选择分区策略:                                       │
│         ├─ 指定分区:直达 🎯                                │
│         ├─ Key分区:hash(key) % n(常用)⭐                 │
│         ├─ 轮询:无Key时轮流分配 🎰                         │
│         └─ 自定义:自己掌控 🎨                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                             ↓
┌─────────────────────────────────────────────────────────────┐
│                    Kafka Topic                              │
│                                                             │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐       │
│  │ Part 0  │  │ Part 1  │  │ Part 2  │  │ Part 3  │       │
│  │ Leader  │  │ Leader  │  │ Leader  │  │ Leader  │       │
│  │ [Msg...]│  │ [Msg...]│  │ [Msg...]│  │ [Msg...]│       │
│  └─────────┘  └─────────┘  └─────────┘  └─────────┘       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                             ↓
┌─────────────────────────────────────────────────────────────┐
│                       消费者组                              │
│                                                             │
│  分区分配策略:                                             │
│  ├─ Range:按范围分,简单 📏                                │
│  ├─ RoundRobin:轮流分,均衡 🎴                             │
│  ├─ Sticky:均衡+粘性,减少迁移 ⭐                          │
│  └─ CooperativeSticky:增量Rebalance,最优 🚀              │
│                                                             │
│  ┌──────────┐   ┌──────────┐   ┌──────────┐               │
│  │Consumer 1│   │Consumer 2│   │Consumer 3│               │
│  │ P0, P1   │   │ P2       │   │ P3       │               │
│  └──────────┘   └──────────┘   └──────────┘               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                             ↓
              触发Rebalance的5种情况:
              ✅ 消费者加入/离开
              ✅ 消费者心跳超时
              ✅ 分区数变化
              ✅ 订阅变化

🎉 恭喜你!

你已经掌握了Kafka分区策略和消费者组负载均衡的全部知识!🎊

记住三个关键点

  1. 生产者:用Key分区保证顺序性 🔑
  2. 消费者:用Sticky策略减少Rebalance影响 🏆
  3. 调优:合理设置超时参数,避免频繁Rebalance ⚙️

下次面试被问到,你就可以像这样回答

"Kafka的分区策略主要有4种:指定分区、Key分区、轮询和自定义分区器。实际项目中我们最常用Key分区,因为它能保证相同Key的消息进入同一个分区,从而保证顺序性。

消费者端我们使用StickyAssignor策略,因为它在Rebalance时能尽量保持原有分配方案,减少分区迁移,避免状态丢失。

为了减少Rebalance的影响,我们会调大session.timeout.ms到30秒,并根据业务处理时间设置max.poll.interval.ms,同时限制max.poll.records避免单次处理时间过长。"

面试官:👍 "很好!你被录取了!"


📚 推荐阅读


🎈 表情包时间 🎈

                  学完Kafka分区策略后的你:

                    😅 → 😐 → 🤔 → 😮 → 😃 → 🤩
               (啥?)(有点懂)(原来如此)(我会了)(太棒了!)

                  vs

                  还没学之前的你:

                         😵‍💫
                    (分区?啥是分区?)

本文完 🎬

记得点赞👍 收藏⭐ 分享🔗

下一篇: 183-Kafka如何保证消息不丢失.md


作者注:这篇文档写得我都饿了,感觉可以开一家"Kafka快递公司"了!🚚📦 如果你觉得这篇文章对你有帮助,请给我一个Star⭐,这是对我最大的鼓励!

版权声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。