Java 調用 Kafka 原生API —— 自定分區使用報錯

此事件非 kafka API調用報錯

在實現自定義分區時,在 partition() 方法中實現分區均衡,均衡策略是根據 value 的 hashcode 值對主題分區個數做取餘,這個均衡策略是很常用的,再利用 nginx 做負載均衡時,就會用到的一種策略。本身沒有任何問題

在做如下測試前,請先啓動 zookeeper 和 kafka,若你的kafka 的server 配置中指定新創建的主題默認爲一個分區,請先創建並修改主題分區數,例如:

./kafka-topics.sh --zookeeper 192.168.0.117:2181 --create --topic yourTopic --replication-factor 1  --partitions 4

代碼

自定義一個分區器,在生產者中的參數設置 "partition.class" 爲 該分區器完整路徑

/**
 * 自定義分區器
 */
public class SelfPartitioner implements Partitioner {
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);//獲取分區
        int num = partitionInfos.size();//
        int parId = value.hashCode() % num;//分區id 均衡
        return parId;
    }

    public void close() {
        System.out.println("自定義分區器 被關閉...");
    }

    public void configure(Map<String, ?> configs) {
        System.out.println("自定義分區器 調用 configure...");
    }
/**
 * 生產者測試代碼
 */
public class SelfPartitionProducer {

    private static KafkaProducer<String,String> producer = null;

    public static void main(String[] args) {
        /*消息生產者*/
        Properties properties = new Properties();
        // kafka 連接 必須參數 
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.0.xxx:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,keySerializeClazz);
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,valueSerializeClazz);

        /*使用自定義的分區器*/
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,SelfPartitioner.class);
        producer = new KafkaProducer<>(properties);
        try {
            for (int i = 0; i < 100; i++) {
                /*待發送的消息實例*/
                ProducerRecord<String, String> record;
                try {
                    record = new ProducerRecord<>("yourTopic", "key-1", "value"+i);
                    //異步發送,三種發送方式都可以
                    producer.send(record,(recordMetadata,e)->{
                        if (null != e){
                            e.printStackTrace();
                        }
                        if (null != recordMetadata) {
                            System.out.println(String.format("偏移量:%s,分區:%s",
                                    recordMetadata.offset(),
                                    recordMetadata.partition()));
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } finally {
            producer.close();
        }
    }
}

結果

過了一定時間(重試超時時間)

如果不適用自定義分區,完全沒有這個問題,但是隻往默認的一個分區發送消息

可以確定,是自定義分區方法報錯

把自定義分區方法分解,debug

public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);//獲取分區
        int num = partitionInfos.size();
        // value 的 hashcode 值在某一個之後 全部變成了負數
        int code = value.hashCode();
        int parId = code % num;
        return value.hashCode()%num;
    }

找到原因: value 爲 Object 對象 hashcode 方法返回值在 某個 value 之後 開始爲負數,導致計算返回的 partition 分區id 爲負數,kafkaProducer 自然找不到 對應的分區,消息無限重試,最後超時

解決方法: 在取得hashcode值之後或者計算分區之後,做是否爲負數的判斷,再取反

public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);//獲取分區
        int num = partitionInfos.size();//
        // Object的hashcode方法在此使用會有極大的問題返回可以是負數,必須轉化爲正數,否則找不到分區
        int code = value.hashCode();
        /*if (code < 0){
            code = - code;
        }*/
        int parId = code % num;
        return parId < 0 ? -parId : parId;
    }

對nginx等類似做負載均衡策略配置的對比:

做nginx負載均衡配置時,若採用這種hashcode取餘算法,沒有符號,在java中有符號,且kafka中分區偏移量都爲正數,需要做相應的取反操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章