在 kafka 中一个 Topic 可以拥有多个 partition , 那么这自然会带来一个问题,当生产者往消息队列中写入数据时,应该往哪个分区中写入数据呢?由此,我们引出生产者分区写入策略
从 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
- 也即,每次产生新的消息,由于轮询策略的存在,每一条消息都被分配到一个不同的分区,相应的,他们也被分配到不同的 batch ,这就导致 batch 被发送到分区时,很可能不是因为数据积累到
-
鉴于此,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 发送消息时,如果对提供的分区策略都不满意,那么可以自定义分区策略,相关步骤如下
- 实现
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) {
}
}
- 在
配置类/ 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