kafka生产者的数据生产流程中,有三个环节是我们可以自定义的,如下图所示。本文为大家介绍如何自定义kafka生产者分区器。
一、生产者默认分区策略
分区策略作用就是指消息根据什么规则,被发往主题的哪个分区。
- 默认策略一:如果生产者指定了partition分区,就直接发送到该partition分区
- 默认策略二:如果没有指定分区但是指定了key,就按照key的hash值选择分区,具有相同key值的消息将被发往同一个分区。
- 默认策略三:如果partition和key都没有指定就使用轮询策略,能保证消息相对均衡的分配给同一个主题下的多个分区。
二、消息有序性的保障
一再说明的是“消息是按照主题分区进行发送的”,所以如果希望保证消息数据发送的有序性,以及消费者消费数据的有序性,就必须将这些消息发往同一个分区。
怎么发往同一个分区?三种方法:
- 生产者指定partition,不建议这种,因为人为指定会造成数据在不同分区之间分配不均。有的分区压力大,有的分区没压力。
- 需要发往同一个分区的消息,指定相同的key,具有相同的hash值,传递到同一个分区。Kafka根据传递消息的key来进行分区的分配,即hash(key) % numPartitions,下文是默认分区源码:
def partition(key: Any, numPartitions: Int): Int = {
Utils.abs(key.hashCode) % numPartitions
}
- 如果通过key的方法,无法满足你的分区需求,我们就可以通过自定义分区器的方式来实现分区逻辑。自定义分区器的优先级最高,将覆盖其他的分区规则
更多的关于kafka生产者保证消息的有序性及其相关原理配置,已经在本专栏的《保证消息顺序性》那一篇文章中介绍,本文不做更多赘述,本文主要为大家介绍如何自定义消息分区器。
三、分区器接口
生产者分区器接口如下,如果希望自定义分区发送逻辑,就需要实现这个接口
package org.apache.kafka.clients.producer;
import org.apache.kafka.common.Configurable;
import org.apache.kafka.common.Cluster;
import java.io.Closeable;
/**
* 分区器接口
*/
public interface Partitioner extends Configurable, Closeable {
/**
* 根据消息record信息对其进行重新分区
*
* @param topic 主题名称
* @param key 用于分区的key对象
* @param keyBytes 用于分区的key的二进制数组
* @param value 生产者消息对象
* @param valueBytes 生产者消息对象的二进制数组
* @param cluster 当前kafka集群的metadata信息
*/
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);
/**
* 当分区器执行完成时被调用
*/
public void close();
default public void onNewBatch(String topic, Cluster cluster, int prevPartition) {
}
}
四、自定义分区器
下面代码实现生产者自定义分区器,实现Partitioner接口。我自定义的逻辑比较简单,如果key=zimug,将数据发往主题中的最后一个分区,否则发往第一个分区。假如某个主题有5个分区,分区编号0-4,那么 如果生产者发送消息数据的key=zimug,数据发往4号分区,否则发往0号分区。
public class MyProducerPartitioner implements Partitioner {
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
//获取topic的partitions信息
List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);
int partitionsNum = partitionInfos.size();
//为特定的key自定义分区规则
if (key != null && key.toString().equals("zimug")) {
//分配到最后一个分区
return partitionsNum - 1;
}else{
//分配到0号分区,即主题中的第一个分区
return 0;
}
}
public void close() {
}
public void configure(Map<String, ?> map) {
}
}
上文这个自定义分区器只是为了讲解自定义生产者分区器,真实生产中不要将数据集中发往主题中的某2个分区,造成数据分配不均。这里只是简单的介绍,不包含实际的应用业务。
五、指定生产者分区器
为生产者指定自定义分区器,这样配置完成之后,生产者再次发送消息时,会遵守MyProducerPartitioner的partition方法中定义的分区规则,将数据发往指定的分区。
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,MyProducerPartitioner.class.getName())