Kafka 的分区和消费者组:一图说不清但能讲明白

米阳 2023-7-8 5 7/8

Kafka 的并发模型就两条规则:一个分区只能被同组的一个消费者消费;不同组之间互不影响。把这两条吃透,很多问题就解释得通了。

分区是并行的单位

一个 topic 分成多个 partition,每个 partition 内部有序,partition 之间无序。消费者数量超过分区数时,多的消费者会空闲——因为没有分区可以分给它。

所以“消费者加到 8 个还是不提速”,先看 topic 有几个分区。3 个分区最多 3 个消费者并行,剩下的都在等。

分区数怎么定

  • 目标吞吐 / 单消费者吞吐 = 最小分区数
  • 分区数只能增不能减,增加会导致 key 到分区的映射变化,顺序性被破坏
  • 分区太多会增加 Broker 的文件句柄和元数据开销,单 topic 几百个分区就要留意了

经验值:中小规模 6~12 个分区,够用且好管理。

消息怎么分配到分区

// 指定 key:同一 key 一定进同一分区,保证同订单有序
producer.send(new ProducerRecord<>("orders", orderId, payload));

// 不指定 key:粘性分区,一批一批轮询,吞吐高但无序
producer.send(new ProducerRecord<>("orders", payload));

只要业务要求“同一实体的事件有序”,就必须用实体 id 做 key。用随机分区再指望消费端排序,是自找麻烦。

消费者组与 rebalance

消费者加入或离开组,会触发 rebalance:分区重新分配,期间所有消费者暂停消费。频繁 rebalance 是生产环境的常见问题。

触发原因:

  1. 消费者处理太慢,超过 max.poll.interval.ms(默认 5 分钟),被踢出组
  2. 心跳线程没发心跳,超过 session.timeout.ms
  3. 消费者重启、扩容

对策:

  • 调大 max.poll.interval.ms,或者减小 max.poll.records,让单次处理时间变短
  • 用 CooperativeStickyAssignor 增量式 rebalance,只迁移必要的分区
  • 静态成员 group.instance.id,重启时不触发 rebalance

offset 提交时机

自动提交(enable.auto.commit=true)默认每 5 秒一次,可能在处理失败前就提交了 offset,导致消息丢失。手动提交更安全:

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
    for (ConsumerRecord<String, String> r : records) {
        process(r);
    }
    consumer.commitSync();
}

批量提交有重复消费的风险(处理完了没提交就崩),所以消费逻辑要幂等。

一句话总结

分区决定并行度,key 决定顺序,消费者组决定隔离。设计的时候先想清楚“哪些消息必须有序”,剩下的才好安排。

- THE END -

米阳

7月08日16:40

最后修改:2023年7月8日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论