教你完成kafka生产者自定义分区策列

325 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

kafka生产者分区

分区的好处

1、可以合理的利用资源,一个Partition只对应一个Broker,一个Broker可以管理多个Partition,方便实现负载均衡。

2、提高并行度,分区个数变多后,生产者可以以分区为单位反射数据,消费者以分区为单位消费数据,同一时间同一消费组内可以有的消费者可以更多,消费能力增强

生产者的分区策略

1、生产者发送消息首先要构造ProducerRecord 对象,该对象Topic和值 Value,主题和值是必须要声明的,Partition分区和Key键可以不用指定,详细的6个构造函数如下。

    public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value, Iterable<Header> headers) {
        if (topic == null) {
            throw new IllegalArgumentException("Topic cannot be null.");
        } else if (timestamp != null && timestamp < 0L) {
            throw new IllegalArgumentException(String.format("Invalid timestamp: %d. Timestamp should always be non-negative or null.", timestamp));
        } else if (partition != null && partition < 0) {
            throw new IllegalArgumentException(String.format("Invalid partition: %d. Partition number should always be non-negative or null.", partition));
        } else {
            this.topic = topic;
            this.partition = partition;
            this.key = key;
            this.value = value;
            this.timestamp = timestamp;
            this.headers = new RecordHeaders(headers);
        }
    }
​
    public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value) {
        this(topic, partition, timestamp, key, value, (Iterable)null);
    }
​
    public ProducerRecord(String topic, Integer partition, K key, V value, Iterable<Header> headers) {
        this(topic, partition, (Long)null, key, value, headers);
    }
​
    public ProducerRecord(String topic, Integer partition, K key, V value) {
        this(topic, partition, (Long)null, key, value, (Iterable)null);
    }
​
    public ProducerRecord(String topic, K key, V value) {
        this(topic, (Integer)null, (Long)null, key, value, (Iterable)null);
    }
​
    public ProducerRecord(String topic, V value) {
        this(topic, (Integer)null, (Long)null, (Object)null, value, (Iterable)null);
    }

ProducerRecord构造函数说明

1、前四个构造方法,指定了partition的具体值,所有发送的消息都会写入到该分区下。

2、第五个没有分区具体的值,但是有key值,这时会计算key的hash值,然后hash值与分区数量取模来得到具体发送到哪一个分区。

例如:key1的hash值=5,topic的partition数=2,那么key1 对应的value1写入1号分区。

3、最后一种没有具体的分区值,也没有key值,那么将使用Sticky Partition(黏性分区器)随机选择一个分区,并且会尽可能一直 使用该分区,等待该分区的batch(批次)满了或者者linger.ms设置的时间到了,Kafka再随机一个分区进行使用(和上一次的分区不同,如果和上一次相同那么会再次随机一个分区,直至不相同)。

分区器

1、上面接受的就是DefaultPartitioner 默认分区。

2、UniformStickyPartitioner 纯粹的粘性分区策略

  • 是不管你有没有key, 统一都用粘性分区来分配

3、RoundRobinPartitioner 分区策略

  • 如果消息中指定了分区,则使用它
  • 将消息平均的分配到每个分区中。
  • 与key无关

自定义分区器

在一些特殊情况下,kafka提供的默认的分区策略不能满足我们的需要,我们就要自己构建我们自己的生产者的分区策略。比如把消息value钟带有hello的发往0号分区,带world的发送1好分区。

步骤

(1)定义类实现 Partitioner 接口。 (2)重写 partition()方法。

(3)使用分区器的方法,在生产者的配置中添加分区器参数,该自定义类的全路径。

public class MyPartitioner implements Partitioner {
    @Override
    public int partition(String s, Object o, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) {
        String msgValue = value.toString();
        // 创建 partition
        int partition;
        // 判断消息是否包含 atguigu
        if (msgValue.contains("hello")){
            partition = 0;
        }else if (msgValue.contains("world")){
            partition = 1;
        }else {
            partition = 2;
        }
        // 返回分区号
        return partition;
    }
​
    @Override
    public void close() {
​
    }
​
    @Override
    public void onNewBatch(String topic, Cluster cluster, int prevPartition) {
        Partitioner.super.onNewBatch(topic, cluster, prevPartition);
    }
​
    @Override
    public void configure(Map<String, ?> map) {
        
    }
    
​
    
// 添加自定义分区器
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.baiyan.kafka.MyPartitioner");

总结

kafka采用分区的模式,可以很方便的实现负载均衡,并且分区数量设置的合理可以提高消费者的并行度,提高消费者的消费效率。