Kafka Rebalance

什么是Rebalance 为了提升消费效率,kafka会将Topic消息放到多个Partition,消费者Consumer可以以消费组(Consumer Group)的形式进行并行消费 在这个背景下,每个消费者节点负责一个或多个Partition。为了保证负载均衡,多个Partition能均匀分配给消费节点,Kafka需要会根据节点负载进行Rebalance以及维护这个映射 为了提升单个Topic的并发性能,将单个Topic拆分为多个Partition 为了提升横向扩展能力,将多个Partition分布在多个不同的Broker 为了提升可用性,为每个Partition加了多个副本 为了协调和管理kafka集群的数据消息,引入Zookeeper作为协调节点 Reblance过程不是瞬间完成的,而是要经历:注册旧分区👉选举Leader👉分配新分区👉初始化消费者 这一完整过程。在这个期间,所有消费者会停止消费 什么时候会触发Rebalance [最常见] 消费者数量发生变化(服务扩缩容的时候、某个节点出问题了重复重启) 调整了Topic的分区数量(kafka只支持增加),新增的分区需要分配消费者 相同消费组下订阅的Topic列表发生了变化 [隐藏的坑] 消费者心跳超过了配置的session.timeout.ms(通常是45s)没有发,kafka会把这个消费者判定为死亡,从而踢出 [隐藏的坑] 消费超时:通常单批消息超过max.poll.interval.ms(通常是5分钟)没有处理完(没有给ack),kafka会把这个消费者踢出 Rebalance期间会引发哪些问题 上述提到,Reblance过程不是瞬间完成的,且这个期间,所有消费者会停止消费,所以会产生消息积压 所以产生积压的时候,优先看下是否发生了Rebalance 消息重复和丢失(见下方) Rebalance 要靠Coordinator协调,频繁触发会占用Kafka集群的CPU和网络资源 而且Kafka默认的分区分配策略(Range 或 RoundRobin),很容易导致负载不均 比如5个分区分配给2个消费者,可能出现 3个分区 vs 2个分区 的情况,其中一个消费者压力翻倍,处理速度变慢,又会触发新的Rebalance,陷入恶性循环 什么时候会丢消息 通常情况下,Reblance不会直接导致丢消息。消费者的处理和offset提交顺序没处理好,就容易出现丢消息的现象 消费者先提交offset(比如100),然后处理消息 开启了自动提交offset(比如100),也是没处理完就提交offset了 上述两种情况,当消费者在处理过程中断了(宕机、OOM、被kill等),导致Kafka触发Rebalance,其它节点接管这个Partition的消费,会从101开始消费 sequenceDiagram participant A as Consumer A participant K as Kafka Broker participant B as Consumer B Note over A,K: 场景:自动提交 (Auto Commit) A->>K: Poll 消息 (Offset 100-200) activate A Note right of A: 放入本地队列/线程池 Note over A,K: 触发自动提交 (5秒到了) A-->>K: 自动提交 Offset = 200 Note right of K: 记录消费进度 = 200 A->>A: 正在处理 Offset 150... Note right of A: 💥 节点突然宕机 (150-199 未处理完) deactivate A Note over K: 心跳丢失 -> Rebalance K->>B: 分配分区给 B B->>K: 获取已提交 Offset K-->>B: 返回 Offset = 200 B->>K: Poll 下一批消息 (201+) Note right of B: 😱 Offset 150-199 永久丢失 正确的做法应该是先处理消息→再提交 offset,确保消息处理完才更新进度。 ...

2025/05/17 · Aris

Kafka vs RocketMQ: 架构、功能、性能

叠个甲:下面的内容来自于小白Debug的三期视频。从到尾看了一遍而整理成的文字稿,并加入了mermaid绘图以及补充了一些内容。下方都已经标明了出处 Kafka架构 内容来源:消息队列Kafka是什么?架构是怎么样的?5分钟快速入门 什么是消息队列 假设你维护了两个服务A和B。B服务每秒只能处理100个消息,但A服务却每秒发出200个消息,有没有办法让B在不被压垮的同时,还能处理掉A的消息? 为了保护B服务,很容易想到在B服务的内存中加入一个队列(说白了其实是个链表)。链表的每个节点就是一个消息,每个节点有一个序号,我们叫它Offset,记录消息的位置。B服务依据自己的处理能力,消费列表里的消息,能处理多少是多少,不断更新已处理消息值。 来不及处理的消息会堆积在内存里,如果B服务更新重启,这些消息就都丢了。这个好解决,将队列挪出来,变成一个单独的进程,就算B服务重启,也不会影响到了队列里的消息。这样一个简陋的队列进程,其实就是所谓的消息队列。 A服务这样负责发数据到消息队列的角色,就是生产者Producer 像B服务这样处理消息的角色就是消费者Consumer。 但这个消息队列属实过于简陋,要对其优化,使其:高性能、高扩展性、高可用 高性能 消息队列里会不断堆积数据,为了提升性能,可以扩展更多的消费者提升消费速度。生产者也可以相对得增加更多,提升了消息队列的吞吐量。 随着生产者和消费者都变多,他们会同时争抢同一个消息队列,抢不到的一方就得等待,这个如何解决? 消息分类(垂直扩展):首先是对消息进行分类,一类是一个Topic。然后根据Topic新增队列的数量,生产者将数据按投递到不同的队列中,消费者则根据需要订阅不同的Topic。这样就大大降低了Topic队列的压力。 分区拆分(水平扩展):但单个Topic的消息可能还是过多,我们可以将单个队列拆成好几段,每段就是一个分区(Partition),每个消费者负责一个Partition,这样就大大降低了争抢,提升了消息队列的性能。 graph LR P[Producer] P -->|Write| T1[TopicUser] P -->|Write| T2[TopicAudit] P -->|Write| T3[...] subgraph Topic Part1[Partition 1] Part2[Partition 2] Part3[Partition 3] Part4[Partition 4] end subgraph ConsumerGroup C1[Consumer 1] C2[Consumer 2] end T1-.-Part1 T1-.-Part2 T1-.-Part3 T1-.-Part4 Part1 -->|Read| C1 Part2 -->|Read| C1 Part3 -->|Read| C2 Part4 -->|Read| C2 高扩展性 随着Partition变多,若Partition都在同一台机器上的话,就会导致单机负载过高,影响整体系统性能。 于是可以申请更多的机器,将Partition分散部署在多台机器上,这每一台机器就代表一个Broker。通过增加Broker,缓解机器负载过高带来的性能问题。 graph TD subgraph Cluster subgraph Broker3 [Broker 3] P1_3["TopicUser-Part3"] P2_1["TopicAudit-Part1"] end subgraph Broker2 [Broker 2] P1_2["TopicUser-Part2"] P2_3["TopicAudit-Part3"] end subgraph Broker1 [Broker 1] P1_1["TopicUser-Part1"] P2_2["TopicAudit-Part2"] end end T1[TopicUser] -.-> P1_1 T1 -.-> P1_2 T1 -.-> P1_3 T2[TopicAudit] -.-> P2_1 T2 -.-> P2_2 T2 -.-> P2_3 classDef userTopic fill:#e1f5fe,stroke:#01579b,stroke-width:2px; classDef userPart fill:#e1f5fe,stroke-width:0px; classDef auditTopic fill:#fff3e0,stroke:#e65100,stroke-width:2px; classDef auditPart fill:#fff3e0,stroke-width:0px; class T1 userTopic; class P1_1,P1_2,P1_3 userPart; class T2 auditTopic; class P2_1,P2_2,P2_3 auditPart; 高可用 如果其中一个Partition所在的Broker挂了,那Broker所有Partition的消息都没了,如何优化? ...

2025/03/15 · Aris