le mie informazioni di contatto
Posta[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Di seguito è riportata una combinazione di casi: conteggio del numero di occorrenze di parole nei messaggi per testare e illustrare il processo di esecuzione dell'elaborazione del flusso di messaggi Kafka
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<exclusions>
<exclusion>
<artifactId>connect-json</artifactId>
<groupId>org.apache.kafka</groupId>
</exclusion>
<exclusion>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
</dependencies>
Per prima cosa scrivi e crea tre classi, rispettivamente come produttori di messaggi, consumatori di messaggi e processori di flusso.
KafkaStreamProducer
:Produttore di messaggi
public class KafkaStreamProducer {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties properties = new Properties();
//kafka的连接地址
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.246.128:9092");
//发送失败,失败的重试次数
properties.put(ProducerConfig.RETRIES_CONFIG, 5);
//消息key的序列化器
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
//消息value的序列化器
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
for (int i = 0; i < 5; i++) {
ProducerRecord<String, String> producerRecord = new ProducerRecord<>("kafka-stream-topic-input", "hello kafka");
producer.send(producerRecord);
}
producer.close();
}
}
Il produttore del messaggio riporta all'argomentokafka-stream-topic-input
Inviato cinque voltehello kafka
KafkaStreamConsumer
: consumatore di messaggi
public class KafkaStreamConsumer {
public static void main(String[] args) {
Properties properties = new Properties();
//kafka的连接地址
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.246.128:9092");
//消费者组
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "group1");
//消息的反序列化器
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
//手动提交偏移量
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
//订阅主题
consumer.subscribe(Collections.singletonList("kafka-stream-topic-output"));
try {
while (true) {
ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println("consumerRecord.key() = " + consumerRecord.key());
System.out.println("consumerRecord.value() = " + consumerRecord.value());
}
// 异步提交偏移量
consumer.commitAsync();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 同步提交偏移量
consumer.commitSync();
}
}
}
KafkaStreamQuickStart
: classe di elaborazione dello streaming
public class KafkaStreamQuickStart {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.246.128:9092");
properties.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
properties.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
properties.put(StreamsConfig.APPLICATION_ID_CONFIG, "streams-quickstart");
StreamsBuilder streamsBuilder = new StreamsBuilder();
//流式计算
streamProcessor(streamsBuilder);
KafkaStreams kafkaStreams = new KafkaStreams(streamsBuilder.build(), properties);
kafkaStreams.start();
}
/**
* 消息格式:hello world hello world
* 配置并处理流数据。
* 使用StreamsBuilder创建并配置KStream,对输入的主题中的数据进行处理,然后将处理结果发送到输出主题。
* 具体处理包括:分割每个消息的值,按值分组,对每个分组在10秒的时间窗口内进行计数,然后将结果转换为KeyValue对并发送到输出主题。
*
* @param streamsBuilder 用于构建KStream对象的StreamsBuilder。
*/
private static void streamProcessor(StreamsBuilder streamsBuilder) {
// 从"kafka-stream-topic-input"主题中读取数据流
KStream<String, String> stream = streamsBuilder.stream("kafka-stream-topic-input");
System.out.println("stream = " + stream);
// 将每个值按空格分割成数组,并将数组转换为列表,以扩展单个消息的值
stream.flatMapValues((ValueMapper<String, Iterable<String>>) value -> {
String[] valAry = value.split(" ");
return Arrays.asList(valAry);
})
// 按消息的值进行分组,为后续的窗口化计数操作做准备
.groupBy((key, value) -> value)
// 定义10秒的时间窗口,在每个窗口内对每个分组进行计数
.windowedBy(TimeWindows.of(Duration.ofSeconds(10)))
.count()
// 将计数结果转换为流,以便进行进一步的处理和转换
.toStream()
// 显示键值对的内容,并将键和值转换为字符串格式
.map((key, value) -> {
System.out.println("key = " + key);
System.out.println("value = " + value);
return new KeyValue<>(key.key().toString(), value.toString());
})
// 将处理后的流数据发送到"kafka-stream-topic-output"主题
.to("kafka-stream-topic-output");
}
}
Questa classe di elaborazione inizia innanzitutto dall'argomentokafka-stream-topic-input
Ottieni i dati del messaggio da e inviali all'argomento dopo l'elaborazionekafka-stream-topic-output
e quindi il consumatore del messaggioKafkaStreamConsumer
consumare
Quando si inserisce un argomento dakafka-stream-topic-input
Durante la lettura di un flusso di dati, ogni messaggio è una coppia chiave-valore.Supponiamo che la chiave del messaggio di input sianull
o una stringa specifica, a seconda di come il messaggio viene inviato all'argomento di input.
KStream<String, String> stream = streamsBuilder.stream("kafka-stream-topic-input");
utilizzoflatMapValues
Il metodo suddivide il valore del messaggio, ma questa operazione non modifica la chiave del messaggio.Se la chiave per inserire il messaggio ènull
, allora in questa fase la chiave del messaggio è fermanull
。
stream.flatMapValues((ValueMapper<String, Iterable<String>>) value -> {
String[] valAry = value.split(" ");
return Arrays.asList(valAry);
})
In Kafka Streams, quando si utilizzagroupBy
Quando un metodo raggruppa un flusso, in realtà specifica una nuova chiave, che verrà utilizzata per le successive operazioni di windowing e di aggregazione.in questo casogroupBy
I metodi vengono utilizzati per raggruppare i messaggi in base al valore:
.groupBy((key, value) -> value)
Ciò significa che dopo l'operazione di raggruppamento, la chiave di ciascun messaggio nel flusso viene impostata sul valore del messaggio.Pertanto, quando seguimap
Visto nel metodokey
parametro, questokey
è in realtà il valore originale del messaggio perché ingroupBy
Successivamente, il valore del messaggio è diventato la chiave.
In questa fase i messaggi vengono suddivisi in finestre e conteggiati, ma le chiavi rimangono invariate.
.windowedBy(TimeWindows.of(Duration.ofSeconds(10)))
.count()
Quando si converte il risultato del conteggio in un flusso, le chiavi rimangono le stesse di prima durante il raggruppamento
.toStream()
esisteremap
metodo, vedikey
Il parametro è in realtà la chiave raggruppata, che è il valore originale del messaggio:
.map((key, value) -> {
System.out.println("key = " + key);
System.out.println("value = " + value);
return new KeyValue<>(key.key().toString(), value.toString());
})
map
nel metodokey.key().toString()
è ottenere la rappresentazione della stringa della chiave, mentrevalue.toString()
consiste nel convertire il valore count in una stringa.
.to("kafka-stream-topic-output");