Kafka-分区

Kafka-分区

kafka的消息时一个个键值对,ProducerRecord对象可以包含目标主题和值,键可以设置为默认的null,不过大多数应用程序会用到键。

键有两个用途;可以作为消息的附加信息,也可以用来决定消息该被写到主题的哪个分区。拥有相同键的消息将被写到同一个分区。

如果键值为null,并且使用了默认的分区器,那么记录将被随机的发送到主题内各个可用的分区上。分区器使用轮询(Round Robin)算法将消息均衡地分不到各个分区上。

如果键不为空,并且使用了默认的分区器,那么kafka会对键进行散列(kafka自己的散列算法),然后根据散列值把消息映射到特定的分区上。同一个键总是被映射到同一个分区上,所以在进行映射时,我们会使用主题所有的分区,而不仅仅是可用的分区。所以,如果写入数据的分区是不可用的,那么就会发生错误。

如果要使用键来映射分区,那么最好在创建主题的时候就把分区规划好,而且永远不要增加新分区。

实现自定义分区策略

除了散列分区之外,有时候也需要对数据进行不一样的分区。

假设你是一个B2B供应商,有一个大客户,它是手持设备Apple的制造商。Apple占据了整体业务的10%的份额。如果使用默认的散列分区算法,Apple的账号记录将和其他账号记录一起被分配给相同的分区,导致这个分区比其它分区要大一些。服务器可能因此出现存储空间不足、处理缓慢等问题。我们需要给Apple分配单独的分区,然后使用散列分区算法处理其他账号。

 代码如下

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

import java.util.List;
import java.util.Map;

/**
 * @Author FengZhen
 * @Date 2020-03-31 22:38
 * @Description 自定义分区
 */
public class ApplePartitioner implements Partitioner {

    @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();
        if (null == keyBytes || !(key instanceof String)) {
            throw  new InvalidRecordException("We expect all messages to have customer name as key");
        }

        if (key.equals("Apple")){
            //分配到最后一个分区
            return numPartitions - 1;
        }
        return Math.abs(Utils.murmur2(keyBytes)) % (numPartitions - 1);
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}

测试

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Properties;

/**
 * @Author FengZhen
 * @Date 2020-03-29 12:21
 * @Description kafka生产者使用
 */
public class KafkaProducerTest {

    private static Properties kafkaProps = new Properties();
    static {
        kafkaProps.put("bootstrap.servers", "localhost:9092");
        kafkaProps.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        kafkaProps.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
    }

    public static void main(String[] args) {
        KafkaProducer<String, String> producer = new KafkaProducer(kafkaProps);
        ProducerRecord<String, String> record = new ProducerRecord<>("test","message_key","message_value");
//        simpleSend(producer, record);
//        sync(producer, record);
//        aync(producer, record);
        udfPartition();
    }

    /**
     * 使用自定义分区
     * ./kafka-topics.sh --create --zookeeper localhost:2181/kafka_2_4_1 --replication-factor 1 --partitions 3 --topic test_partition
     */
    public static void udfPartition(){
        kafkaProps.put("partitioner.class", "com.chinaventure.kafka.partition.ApplePartitioner");
        KafkaProducer<String, String> producer = new KafkaProducer(kafkaProps);
        for (int i = 0; i < 10; i++){
            ProducerRecord<String, String> record = new ProducerRecord<>("test_partition",i % 3 == 0 ? "Apple": "Banana"+i,"我是" + i);
            producer.send(record, new DemonProducerCallback());
        }
        while (true){
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 最简单的方式发送,不管消息是否正常到达
     * @param producer
     */
    public static void simpleSend(KafkaProducer producer, ProducerRecord record){
        try {
            producer.send(record);
        } catch(Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 同步发送
     * @param producer
     * @param record
     */
    public static void sync(KafkaProducer producer, ProducerRecord record){
        try {
            RecordMetadata recordMetadata = (RecordMetadata) producer.send(record).get();
            System.out.println("topic:" + recordMetadata.topic());
            System.out.println("partition:" + recordMetadata.partition());
            System.out.println("offset:" + recordMetadata.offset());
            System.out.println("metaData:" + recordMetadata.toString());
        } catch(Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 异步发送
     * @param producer
     * @param record
     */
    public static void aync(KafkaProducer producer, ProducerRecord record){
        try {
            producer.send(record, new DemonProducerCallback());
            while (true){
                Thread.sleep(10 * 1000);
            }
        } catch(Exception e){
            e.printStackTrace();
        }
    }

    private static class DemonProducerCallback implements Callback {
        @Override
        public void onCompletion(RecordMetadata recordMetadata, Exception e) {
            if (null != e){
                e.printStackTrace();
            }else{
                System.out.println("topic:" + recordMetadata.topic());
                System.out.println("partition:" + recordMetadata.partition());
                System.out.println("offset:" + recordMetadata.offset());
                System.out.println("metaData:" + recordMetadata.toString());
            }

        }
    }
}

查看最后一个分区的日志

FengZhendeMacBook-Pro:bin FengZhen$ cat /tmp/kafka-logs/test_partition-2/00000000000000000000.log 
}??k?q1?q1???????????????$
Apple我是0$&
Apple我是3$&
Apple我是6$&
Apple我是9
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值