kafka深度讲解

逻辑结构

┌───────────────────────────────────────────────────────────

│ Kafka Cluster │

│ │

│ ┌---------- Broker-1 ----------┐ ┌---------- Broker-2 ----------┐

│ | Topic-A |

│ | partition-0 LEADER (ISR={0,1}) partition-1 FOLLOWER |

│ | partition-2 FOLLOWER partition-0 LEADER(...) |

│ └------------------┬-------------------┘ └------------------┬--------┘

│ │ 心跳/元数据/控制器(Controller) │

└─────────────────────┼─────────────────────────────────────

核心概念速记

  • Cluster 是集群,多台服务器
  • Broker 是节点,比如(192.168.0.1 192.168.1.1)
  • Topic 逻辑消息主题,类似数据库表
  • Partition 是Topic 的物理分片,顺序与并行的最小单元,可以理解为管道或者队列
  • Replica 分区副本
  • Consumer Group 消费者组,组内分区独占,组间广播

创建 Topic

powershell 复制代码
./kafka-topics.sh \
--bootstrap-server localhost:9092 \    
--create \
--topic order-topic \
--partitions 3 \
--replication-factor 1

解释:

  • order-topic Topic名称
  • partitions=3 创建3个Partition, Partition 编号:0、1、2
  • replication-factor 1 表示该主题的每个分区只保存一份数据,没有副本,--replication-factor 1,意味着该主题的每个分区只有 Leader,没有任何 Followe

查看 Topic

perl 复制代码
./kafka-topics.sh \
--bootstrap-server localhost:9092 \
--describe \
--topic order-topic

输出类似:

perl 复制代码
Topic: order-topic

PartitionCount: 3

Partition:0
Partition:1
Partition:2

创建生产者

perl 复制代码
./kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic order-topic \
--property parse.key=true \
--property key.separator=:

解释 :

  • property "parse.key=true" 启用消息的键值解析模式。默认情况下,生产者只发送消息体(值)。加上该参数后,控制台会读取用户输入的每一行,并按分隔符拆分为键(Key)和值(Value)
  • property "key.separator=:" 指定键与值之间的分隔符为冒号 :。结合上一行,当你在控制台输入类似 order123:{"id":1} 的内容时,order123 会被作为消息的 Key,而 {"id":1} 作为消息的 Value 发送到 Kafka。

输入

perl 复制代码
order123:{"status":"CREATED"}
order123:{"status":"PAID"}
order456:{"status":"CREATED"}

Kafka 默认分区路由规则(重点)

perl 复制代码
partition = hash(key) % numPartitions

把key进行hash,再对Partitions的数量取模

你的消息会进哪个 Partition?

perl 复制代码
Key			hash(key)	%3	  进哪个 Partition
order123	hash1		1	  ✅ Partition-1
order123	hash1		1	  ✅ Partition-1
order456	hash2		0	  ✅ Partition-0

结论

  • ✅ order123的两条消息 一定在同一个队列
  • ✅ order456在另一个队列
  • ✅ 顺序性得到保证(按 key)

当你再发 4 个不同 Key 时会发生什么?

perl 复制代码
order4:{"status":"CREATED"}
order6:{"status":"CREATED"}

现在你有 4 个 Key,3 个 Partition

perl 复制代码
Key	            hash		   	%3		Partition
order123        h1				 1		    P1
order456		h2		 		 0		    P0
order4			h3		 	     2		    P2
order6			h4		 		 1		    P1

结果

  • 多个 Key 一定会共用同一个 Partition
  • 这不是 Bug,而是 设计如此
  • Kafka 不会为每个 Key 单独建
    Partition

如何让「指定消息」进入「指定队列」?

这样 所有消息强行进 Partition-2(⚠️ 一般不推荐,破坏均衡)。

perl 复制代码
bin/kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic order-topic \
--property parse.key=true \
--property key.separator=: \
--property partition=2

Kafka 的三种分区路由方式

(1)直接指定 Partition

bash 复制代码
bin/kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic order-topic \
--property parse.key=true \
--property key.separator=: \
--property partition=2

解释: 将所有消息全部放入Partition-2

(2) 自定义 Partitioner

比如:

  • 订单号末位 → Partition
  • 城市编码 → Partition
  • 业务类型 → Partition

java

java 复制代码
public class OrderPartitioner implements Partitioner {

    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                         Object value, byte[] valueBytes, Cluster cluster) {
        int numPartitions = cluster.partitionCountForTopic(topic);

        if ("order456".equals(key)) {
            return 0; // 强制进 partition 0
        }

        // 其他 key 走默认 hash
        return Math.abs(Utils.murmur2(keyBytes)) % numPartitions;
    }

    @Override public void close() {}
}

(3)Key-based Hash(默认策略)

bash 复制代码
partition = hash(key) % partitions

那为什么大家"以为 Kafka 只能用 Hash"?

因为这是 99% 的业务用法

如果key是null进入那个partition ?

你的操作顺序

bash 复制代码
# 1️⃣ 创建 topic(3 partitions)
./kafka-topics.sh \
--bootstrap-server localhost:9092 \
--create \
--topic order-topic \
--partitions 3 \
--replication-factor 1```

```bash
# 2️⃣ 启动 producer(开启 key)
./kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic order-topic \
--property parse.key=true \
--property key.separator=:

最终输出

bash 复制代码
order1:{"status":"CREATED"}
order2:{"status":"PAID"}
order3:{"status":"CREATED"}
{"status":"CREATED"}

黏性分区

bash 复制代码
kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic order-topic \
--property parse.key=true \
--property key.separator=:

输入

bash 复制代码
order1:{"status":"CREATED"}   ✅ Key 有值 → Hash → P1
order2:{"status":"PAID"}      ✅ Key 有值 → Hash → P2
order3:{"status":"CREATED"}   ✅ Key 有值 → Hash → P0
{"status":"CREATED"}          ❌ Key 为 null → Sticky → P0

发生什么?

  • 发完 order3后,Producer 当前粘住的 Partition 是 P0
  • 接着发 key=null的消息✅ 直接复用 P0
  • 不换 Partition,不重新选

什么是"消费分组"(Consumer Group) ?