8、kafka生产者自定义分区算法实例

一、kafka消息发送流程

kafka生产者在发送消息时候,key指定后可以使用自己的分区算法,KafkaProducer在调用send方法发送消息至broker的过程中,首先是经过拦截器Inteceptors处理,然后是经过序列化Serializer处理,之后就到了Partitions阶段,即分区分配计算阶段。在某些应用场景下,业务逻辑需要控制每条消息落 到合适的分区中,有些情形下则只要根据默认的分配规则即可。在KafkaProducer计算分配时,首先根据的是ProducerRecord中的partition字段指定的序号计算分区,KafkaProducer中还支持自定义分区分配方式,实现org.apache.kafka.clients.producer.Partitioner接口.

二、分区接口实现

具体实现接口如下

package com.donwait.producer;

import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;

public class SimplePartitioner implements Partitioner {
    private final AtomicInteger atomicInteger = new AtomicInteger(0);
    
    @Override
    public void configure(Map<String, ?> configs) {
        // TODO Auto-generated method stub

    }

    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        // 从集群中获取所有分区信息
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int numPartitions = partitions.size();
        
        // key为null或空时使用轮训分区做负载均衡
        if (null == keyBytes || keyBytes.length < 1) {
            return atomicInteger.getAndIncrement() % numPartitions;
        }
        
        // 借用String的hashCode的计算方式
        int hash = 0;
        for (byte b : keyBytes) {
            hash = 31 * hash + b;
        }
        return hash % numPartitions;
    }

    @Override
    public void close() {
        // TODO Auto-generated method stub

    }

}

该接口实现有三个方法,一个configure,一个close,一个partition,configure在初始化的时候调用一次,close在关闭的时候调用一次,partition在每一次发送过程中都会调用。这里自己实现的分区算法是:但key为null或为空,使用轮训算法做负载均衡,否则使用hash算法做负载均衡。

三、使用自定义分区算法

package com.donwait.producer;

import java.util.Properties;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

public class ProducerTest {
    private KafkaProducer<String, String> producer = null;
    
    // ag:192.168.12.150:9092,192.168.12.151:9092
    public ProducerTest(String brokerList){
        Properties props = new Properties();
        // kafka服务器地址: IP:PORT形式,多个以逗号隔开
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, brokerList);
        // kafka消息key序列化类 若传入key的值,则根据该key的值进行hash散列计算出在哪个partition上  
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        // kafka消息序列化类 即将传入对象序列化为字符【可以为字节数组 】
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        // 批处理大小0则完全禁用批处理
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 0);        
        // 对应partition的leader写到本地后即返回成功。极端情况下,可能导致失败
        // 0从不等待消息确认,1只需leader副本接收到消息就发送确认,-1所有副本接收完成后发送确认消息
        props.put(ProducerConfig.ACKS_CONFIG, "1");
        
        //request.required.acks
        // 0, which means that the producer never waits for an acknowledgement from the broker (the same behavior as 0.7). This option provides the lowest latency but the weakest durability guarantees (some data will be lost when a server fails).
        // 1, which means that the producer gets an acknowledgement after the leader replica has received the data. This option provides better durability as the client waits until the server acknowledges the request as successful (only messages that were written to the now-dead leader but not yet replicated will be lost).
        //-1, which means that the producer gets an acknowledgement after all in-sync replicas have received the data. This option provides the best durability, we guarantee that no messages will be lost as long as at least one in sync replica remains.
        //props.put("request.required.acks","-1");
        
        //     props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        // 生产者发送失败后的重试次数,默认0
        //  props.put(ProducerConfig.RETRIES_CONFIG, 0);
        // 生产者将等待达到给定延迟以允许发送其他记录,以便发送可以一起批量发送,TCP中的Nagle算法类似
        //  props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        // 生产者可用于缓冲等待发送到服务器的记录的总内存字节数
        //  props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);

        // 是否使用自定义的分区计算方法
        props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.donwait.producer.SimplePartitioner");
        producer = new KafkaProducer<String, String>(props);
    }
    
    /*
     * 当发送消息时我们不指定key时,producer将消息分发到各partition的机制是:
        Scala版本的producer:
        在你的producer启动的时候,随机获得一个partition,然后后面的消息都会发送到这个partition,
        也就是说,只要程序启动了,这个producer都会往同一个partition里发送消息,
        所以,当使用Scala版本的producer时,尽量传入key,保证消息在partition的平均性
        
        java版本的producer:
        会轮询每个partition,所以发送的会比较平均
     */
    public void send(String topic, String key, String data, boolean sync){

        if (sync) {
            try {
                // 实际上调用send方法并不能保证消息被成功写入到kafka
                // 为了实现同步的发送消息,并监控每条消息是否发送成功,需要对每次调用send方法后返回的Future对象调用get方法
                // get方法的调用会导致当前线程block,直到发送结果返回,不管是成功还是失败,key指定后可以使用自己的分区算法
                // KafkaProducer在调用send方法发送消息至broker的过程中,首先是经过拦截器Inteceptors处理,然后是经过序列化
                // Serializer处理,之后就到了Partitions阶段,即分区分配计算阶段。在某些应用场景下,业务逻辑需要控制每条消息落
                // 到合适的分区中,有些情形下则只要根据默认的分配规则即可。在KafkaProducer计算分配时,首先根据的是ProducerRecord
                // 中的partition字段指定的序号计算分区,KafkaProducer中还支持自定义分区分配方式,实现org.apache.kafka.clients.producer.Partitioner接口
                producer.send(new ProducerRecord<String, String>(topic, key, data)).get();
            } catch (Exception e) {
                // 当get方法抛出一个错误时,说明数据没有被正确写入,此时需要处理这个错误。
                e.printStackTrace();
            }
        } else {
            producer.send(new ProducerRecord<String, String>(topic, key, data));
        }

        // 必须写下面这句,相当于发送
        producer.flush();
    }
}

四、入口函数实现

package com.donwait;
import com.donwait.producer.ProducerTest;
public class Demon {
     
     public static void main(String[] args) {
          ProducerTest producer = new ProducerTest("192.168.12.150:9092,192.168.12.151:9092,192.168.12.152:9092");
          producer.send("test-topic", "key1", "来自我的java测试程序~", true);
          System.out.println("发送完成!");
          
//        ConsumerTest consumer = new ConsumerTest();
//        consumer.consumer1("192.168.12.150:9092,192.168.12.151:9092,192.168.12.152:9092", "myConsumerGroup", "test-topic");
//        System.out.println("消费完成~");
     }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝壳里的沙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值