此事件非 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中分區偏移量都爲正數,需要做相應的取反操作