前言
在實際使用中,我們可能需要對某個topic下不同的消息進行分類管理,比如確保消費的順序性,在這種場景下,我們可以首先確保生產者發送消息到指定的分區即可
本文的測試基於docker搭建的一個雙節點的簡單集羣,有興趣搭建的同學可參考我的另一篇博客
1、創建一個名爲second的topic
在該topic下,有3個分區,兩個副本
$KAFKA_HOME/bin/kafka-topics.sh --create --zookeeper zoo1:2181 --replication-factor 2 --partitions 3 --topic second
2、從某個docker節點下進入控制檯,輸入如下命令等待消費
$KAFKA_HOME/bin/kafka-console-consumer.sh --bootstrap-server kafka1:9092 --from-beginning --topic second
在使用Java客戶端連接kafka進行消息發送時,提供了2種發送消息到指定的分區的方式,下面分別進行演示
3、pom文件添加如下依賴
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.11.0.0</version>
</dependency>
方式1:直接在發送消息時指定
下面貼出生產者的主要代碼
/**
* 生產者將消息發到指定的主題分區下
*/
public class SpecialPartionProducer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("bootstrap.servers", "106.15.37.147:9092")
properties.put("acks", "all");
properties.put("retries", "3");
properties.put("batch.size", "16384");
properties.put("linger.ms", 1);
properties.put("buffer.memory", 33554432);
//key和value的序列化
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//構造生產者對象
KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
//發送消息
for (int i = 0; i < 10; i++) {
producer.send(new ProducerRecord<String, String>("second", 0,"congge " ,"val = "+ i)
, new ProducerCallBackV2());
}
//關閉連接資源
producer.close();
}
}
/**
* 生產者回調消息
*/
class ProducerCallBackV2 implements Callback {
public void onCompletion(RecordMetadata metadata, Exception e) {
if(e == null){
System.out.println("offset : " + metadata.offset());
System.out.println("partition : " + metadata.partition());
System.out.println("topic : " +metadata.topic());
System.out.println("===============================");
}
}
}
在這種方式下,我們只需要在producer.send()方法中指定具體的分區值即可,運行這段代碼,從控制檯可以看到,消息發送到分區爲0的裏面
方式2:實現Partitioner接口
這種方式看起來更加靈活,重寫裏面的partition方法,可以更好的結合具體的業務場景對分區進行指定,因此首先需提供一個自定義的分區類,假如我這裏直接返回分區1,當然,也可以通過一定的取模算法,或者根據業務逐漸寫個路由算法進行指定也可
public class MyPartion implements Partitioner {
public int partition(String s, Object o, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
return 1;
}
public void close() {
}
public void configure(Map<String, ?> map) {
}
}
生產者代碼,這一次,通過上面的這種方式做,則需要在參數裏面進行分區的指定,即只需要將實現上面Partitioner接口的完整的類加上即可
public class PartionProducer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("bootstrap.servers", "106.15.37.147:9092");
properties.put("acks", "all");
properties.put("retries", "3");
properties.put("batch.size", "16384");
properties.put("linger.ms", 1);
properties.put("buffer.memory", 33554432);
//key和value的序列化
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//添加自定義分區器
properties.put("partitioner.class", "com.congge.partion.MyPartion");
//構造生產者對象
KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
//發送消息
for (int i = 0; i < 10; i++) {
producer.send(new ProducerRecord<String, String>("second", "congge-self ", "val = " + i)
, new ProducerCallBackV3());
}
//關閉連接資源
producer.close();
}
}
/**
* 生產者回調消息
*/
class ProducerCallBackV3 implements Callback {
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null) {
System.out.println("offset : " + metadata.offset());
System.out.println("partition : " + metadata.partition());
System.out.println("topic : " + metadata.topic());
System.out.println("===============================");
}
}
}
運行上面的代碼,觀察控制檯輸出,同樣也能達到預期的效果