1.概述
Kafka Streams是一个用于构建应用程序和微服务的客户端库,其中的输入和输出数据存储在Kafka集群中。它结合了在客户端编写和部署标准Java和Scala应用程序的简单性,以及Kafka服务器端集群技术的优点。
- Topology(拓扑):表示一个流计算任务,等价于MapReduce中的job。不同的是MapReduce的job作业最终会停止,但是ToPology会一直运行在内存中,除非人工关闭该Topology。
- Stream:它代表了一个无限的,不断更新的Record数据集。流是有序的,可存放和容错的不可变数据记录序列,其中数据记录定义为键值对。
- States:可以持久化存放流计算状态结果,可以用以容错和故障恢复。
- Time:
- Event Time(事件时间)
- Processing Time(处理时间)
- Ingestion Time(摄入时间)
注意:所谓的流处理就是通过Topology编织程序对Stream中的Record元素的处理的逻辑/流程。
2.架构
kafka Streams通过构建kafka生产者和消费者并利用kafka的本机功能来提供数据并行性,分布式协调,容错和操作简便性,从而简化了应用程序开发。
kafka的消息分区用于存储和传递消息,kafka Streams对数据进行分区以进行处理。kafka Streams 使用Partition和Task的概念作为基于kafka Topic分区的并行模型的逻辑单元。在并行化的背景下,kafka Streams和kafka之间有着密切的联系:
- 每个Stream分区都是完全有序的数据记录序列,并映射到kafka Topic分区。
- Stream中的数据记录映射到该Topic的kafka消息。
- 数据记录的key决定了kafka Streams中数据的分区,即数据如何路由到Topic的特定分区。
3.任务的并行度
Kafka Streams基于应用程序的输入流分区创建固定数量的Task,每个任务(Task)分配来自输入流的分区列表(即Kafka主题)。分区到任务的分配永远不会改变,因此每个任务都是应用程序的固定平行单元。然后,任务可以根据分配的分区实例化自己的处理器拓扑; 它们还为每个分配的分区维护一个缓冲区,并从这些记录缓冲区一次一个地处理消息。因此,流任务可以独立并行地处理,无需人工干预。
用户可以启动多个KafkaStream实例,这样等价启动了多个Stream Tread,每个Thread处理1~n个Task。一个Task对应一个分区,因此Kafka Stream流处理的并行度不会超越Topic的分区数。需要值得注意的是Kafka的每个Task都维护这自身的一些状态,线程之间不存在状态共享和通信。因此Kafka在实现流处理的过程中扩展是非常高效的。
4.容错
Kafka Streams构建于Kafka本地集成的容错功能之上。 Kafka分区具有高可用性和复制性
;因此当流数据持久保存到Kafka时,即使应用程序失败并需要重新处理它也可用。 Kafka Streams中的任务利用Kafka消费者客户端提供的容错功能来处理故障。如果任务运行的计算机故障了,Kafka Streams会自动在其余一个正在运行的应用程序实例中重新启动该任务。
此外,Kafka Streams还确保local state store
也很有力处理故障容错。对于每个state store,Kafka Stream维护一个带有副本changelog
的Topic,在该Topic中跟踪任何状态更新。这些changelog Topic也是分区的,该分区和Task是一一对应的。如果Task在运行失败并Kafka Stream会在另一台计算机上重新启动该任务,Kafka Streams会保证在重新启动对新启动的任务的处理之前,通过重播相应的更改日志主题,将其关联的状态存储恢复到故障之前的内容。
5.使用kafka Streming完成单词计数的实时统计
注:创建kafka Streaming Topology有两种方式
1.low-level:Processor API
2.high-level:Kafka Streams DSL(DSL:提供了通用的数据操作算子,如:map, filter, join, and aggregations等)
5.1 创建Maven工程,引入相关依赖
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>2.2.0</version>
</dependency>
5.2 编写自定义处理器
package com.hw.lowlevel;
import org.apache.kafka.connect.data.Timestamp;
import org.apache.kafka.streams.processor.Processor;
import org.apache.kafka.streams.processor.ProcessorContext;
import org.apache.kafka.streams.processor.PunctuationType;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* @aurhor:fql
* @date 2019/8/31 16:22
* @type:
*/
public class WordCountProcessor implements Processor<Integer,String> {
//流处理的中间结果(状态)
private Map<String ,Long> wordcount;
private ProcessorContext context;
/**
* 初始化方法
* @param context 上下文环境信息
*/
@Override
public void init(final ProcessorContext context) {
wordcount=new HashMap<>();
this.context=context;
//将单词统计的结果发送给下游的sink输出
this.context.schedule(Duration.ofSeconds(1), PunctuationType.STREAM_TIME,(Timestamp->{
wordcount.forEach((word,num)->{
context.forward(word,num);
});
}));
}
/**
*
* @param key
* @param value 持续输入的流数据 ,一行英文短语
*/
@Override
public void process(Integer key, String value) {
String[] words = value.split(" ");
for (String word : words) {
Long num = wordcount.getOrDefault(word, 0L);
num++;
wordcount.put(word,num);
}
//标记处理结束
this.context.commit();
}
@Override
public void close() {
}
}
5.3 编写Stream
package com.hw.lowlevel;
import org.apache.kafka.common.serialization.LongSerializer;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.common.serialization.StringSerializer;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.Topology;
import java.util.Properties;
/**
* @aurhor:fql
* @date 2019/9/1 9:17
* @type: 实时的单词计数
*/
public class WordCountApplication {
public static void main(String[] args) {
//1.创建一个配置对象
Properties properties = new Properties();
properties.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,"node1:9092,node2:9092,node3:9092");
//key的序列化和反序列化
properties.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.Integer().getClass());
//vaule反序列化和反序列化
properties.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,Serdes.String().getClass());
//应用的ID=拉取消费组的ID
properties.put(StreamsConfig.APPLICATION_ID_CONFIG,"wordCount");
//流处理应用 开启的线程数量
properties.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG,2);
//手动绘制拓扑
Topology topology = new Topology();
topology.addSource("s1","hw");//指定数据源
topology.addProcessor("p1",()->new WordCountProcessor(),"s1");//指定处理器
topology.addSink("k1","t8",new StringSerializer(),new LongSerializer(),"p1");
//初始化kafka流处理任务
KafkaStreams kafkaStreams = new KafkaStreams(topology, properties);
//运行流处理程序
kafkaStreams.start();
}
}
5.4 进行测试
- 启动数据源输入测试数据
- 启动订阅t8查看统计结果
启动指令
bin/kafka-console-consumer.sh --bootstrap-server node1:9092,node2:9092,node3:9092 \
--topic t8 \ (指定输出的主题)
--from-beginning \
--formatter kafka.tools.DefaultMessageFormatter \
--property print.key=true \
--property print.value=true \
--property key.deserializer=org.apache.kafka.common.serialization.StringDeserializer \ (输出结果的反序列化)
--property value.deserializer=org.apache.kafka.common.serialization.LongDeserializer