Kafka 生产者分区写入策略

1,488 阅读4分钟

在 kafka 中一个 Topic 可以拥有多个 partition , 那么这自然会带来一个问题,当生产者往消息队列中写入数据时,应该往哪个分区中写入数据呢?由此,我们引出生产者分区写入策略

image.png

DefaultPartitioner 类中可以看到,生产者分区写入策略分为三种情况

  • *If a partition is specified in the record, use it —— 指定分区策略
  • *If no partition is specified but a key is present choose a partition based on a hash of the key —— 哈希策略
  • *If no partition or key is present choose the sticky partition that changes when the batch is full —— 黏性分区策略

指定分区策略

  • 使用消息的键(key)进行哈希计算,根据哈希结果将消息分配到特定的分区。
  • 具有相同键的消息将始终被分配到同一个分区,保证了相同键的消息在分区内的顺序性。
  • 适用于需要保持特定消息顺序或进行消息聚合的场景。
  • 以 kafka 整合 springboot 为例,只需要在 kafkaTemplate.send() 指定 partition 就能实现该策略
Integer partition = 0;
String message = "message";
String key = null;
kafkaTemplate.send(topicName, partition, key, message);

哈希策略(Hash)

  • 使用消息的键(key)进行哈希计算,根据哈希结果将消息分配到特定的分区。

    • 没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值
    • 例如:key01 的 hash = 5,key02 的 hash 值=6,topic 的 partition 数=2,那么 key01 对应的 value01 写入 1 号分区,key02 对应的 value02 写入 0 号分区。
  • 具有相同键的消息将始终被分配到同一个分区,保证了相同键的消息在分区内的顺序性。

  • 适用于需要保持特定消息顺序或进行消息聚合的场景。

  • 以 kafka 整合 springboot 为例,只需要在 kafkaTemplate.send() 指定 key 且 partition = null 就能实现该策略

Integer partition = null;
String message = "message";
String key = key;
kafkaTemplate.send(topicName, partition, key, message);

粘性分区策略

  • 《Kafka 生产者发送消息的流程》中,我们介绍到 如果每一个消息都单独穿行于网络,会导致大量的网络开销,把消息分成批次传输可以减少网络开销为了提高效率,消息被分批次写入kafka。一个批次就是一组消息,这些消息(batch)属于同一个主题和分区。由此会带来一个问题

分批发送的背景下轮询策略带来的问题

  • 在在上面所述的背景下,我们希望每次都尽可能填满一个batch再发送到一个分区

  • 如果既没有partition值又没有key值时采用轮询策略(Kafka2.4版本之前默认的分区写入策略,生产者将消息按照顺序轮流写入每个可用的分区,实现简单的负载均衡)可能会造成一个大的batch被轮询成多个小的batch的情况

    • 也即,每次产生新的消息,由于轮询策略的存在,每一条消息都被分配到一个不同的分区,相应的,他们也被分配到不同的 batch ,这就导致 batch 被发送到分区时,很可能不是因为数据积累到 batch.size ,而是因为超过时间限制 linger.ms
  • 鉴于此,Kafka2.4 的时候推出一种新的分区策略——Sticky Partition(黏性分区器)

    • Sticky Partition(黏性分区器)会随机选择一个分区,并尽可能一直使用该分区,待该分区的 batch 已满或者已完成,Kafka再随机一个和上一次的分区不同的分区进行使用
    • 例如:第一次随机选择 0 号分区,等 0 号分区当前批次满了(积累到 batch.size )或者linger.ms 设置的时间到,Kafka再随机一个分区进行使用(如果还是0会继续随机)。
  • 以 Kafka 整合 springboot 为例,在 kafkaTemplate.send() 只指定 topicName 即默认使用该策略

kafkaTemplate.send(topicName);

自定义分区策略

  • 在使用 Spring Kafka 中的 KafkaTemplate 发送消息时,如果对提供的分区策略都不满意,那么可以自定义分区策略,相关步骤如下
  1. 实现 org.apache.kafka.clients.producer.Partitioner
/**
 * @author WARRIOR
 * @version 1.0
 * 自定义分区策略类
 */

public class CustomPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        // TODO 实现自定义的生产者分区策略,发送到的分区编号
    }

    @Override
    public void close() {
        
    }

    @Override
    public void configure(Map<String, ?> configs) {
    }
}
  1. 配置类/ appilication.yml/appilication.**properties** 配置文件中传递自定义的分区策略类,如下
/**
 * @author WARRIOR
 * @version 1.0
 * kafka 配置类
 */

@Configuration
public class KafkaConfig {

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
				// TODO 添加其他配置信息
        configProps.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, CustomPartitioner.class);
        return new DefaultKafkaProducerFactory<>(configProps);
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

}

或者

spring:
  # kafka 相关配置
  kafka:
    producer:
      properties:
        partitioner:
          class: com.warrior.kafka.partitioner.CustomPartitioner