流计算定义
一般流计算会与批量计算相比较。在流式计算模型中,输入是持续的,可以认为在时间上是无界的,也就意味着,永远拿不到全量数据去计算。同时,计算结果是持续输出的,也即计算结果在时间上也是无界的。流式计算一般对实时性要求较高,同时一般是先定义目标计算,然后数据到来之后将计算逻辑应用于数据。同时为了提高计算效率,往往尽可能采用增量计算代替全量计算。批量处理模型中,一般先有全量数据集,然后定义计算逻辑,并将计算应用于全量数据。特点是全量计算,并且计算结果一
次性全量输出
Kafka Stream
Kafka Streams是一个客户端库,用于处理和分析存储在Kafka中的数据。它建立在重要的流处理概念之
上,正确区分EventTime和ProcessTime,Widows计算,可以实现对应用状态高效管理和实时查询。Kafka
Streams进入门槛低。可以在单机上验证流处理的概念。同时可以利用Kafka的并行加载模型,实现流处
理并行扩展,也就意味着用户只需要将自己流处理程序运行多份即可达到并行计算的目的。
Kafka Streams优点:简单、轻巧易部署、无缝对接Kafka、基于分区实现计算并行、基于幂等和事务特
性实现精确计算、单个记录毫秒级延迟计算-实时性高、提供了两套不同风格的流处理API-(High levelDomain Specific Language|DSL开箱即用;low-level Processor API.)
名词解析
Topology:表示一个流计算任务,等价于MapReduce中的job。不同的是MapReduce的job作业最终会停
止,但是Topology会一直运行在内存中,除非人工关闭该Topology。
stream:它代表了一个无限的,不断更新的Record数据集。流是有序,可重放和容错的不可变数据记录
序列,其中数据记录被定义为键值对。
所谓的流处理是通过Topology编织程序对stream中Record元素的处理的逻辑/流程。这种计算和早期
MapReduce计算的最大差异是该计算的实时性比较高,可以满足绝大多数的实时计算场景。Kafka Stream
以它的轻量级、容易部署、低延迟等特点在微服务领域相比较 专业的 Storm、spark streaming和Flink 而
言有着不可替代的优势。有关Storm、SparkStreaming和Flink的内容随着课程的深入会在后续章节再展开
讨论。
架构
Kafka Streams通过构建Kafka生产者和消费者库并利用Kafka的本机功能来提供数据并行性,分布式协调,容错和操作简便性,从而简化了应用程序开发。
Kafka的消息分区用于存储和传递消息, Kafka Streams对数据进行分区以进行处理。 Kafka Streams使用
partition和Task的概念作为基于Kafka Topic分区的并行模型的逻辑单元。在并行化的背景下,Kafka
Streams和Kafka之间有着密切的联系:
- 每个stream分区都是完全有序的数据记录序列,并映射到Kafka Topic分区。
- stream中的数据记录映射到该Topic的Kafka消息
- 数据记录的key决定了Kafka和Kafka Streams中数据的分区,即数据如何路由到Topic内的特定分区。
任务并行度
应用程序的处理器Topology通过将其分解为多个Task来扩展。更具体地说,Kafka Streams基于应用程序
的输入流分区创建固定数量的任务,每个任务分配来自输入流的分区列表。分区到任务的分配永远不会
改变,因此每个任务都是应用程序的固定平行单元。然后,任务可以根据分配的分区实例化自己的
Topology;它们还为每个分配的分区维护一个缓冲区,并从这些记录缓冲区一次一个地处理消息。因此,
流任务可以独立并行地处理,无需人工干预。
用户可以启动多个KafkaStream实例,这样等价启动了多个Stream Tread,每个Thread处理1~n个Task。一
个Task对应一个分区,因此Kafka Stream流处理的并行度不会超越Topic的分区数。需要值得注意的是
Kafka的每个Task都维护这自身的一些状态,线程之间不存在状态共享和通信。因此Kafka在实现流处理
的过程中扩展是非常高效的。
容错
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会保证在重新启动对新启动的任务的处理之前,通过重播相应的更改日志主题,将其
关联的状态存储恢复到故障之前的内容。
实战编程
所有资料均参考:https://kafka.apache.org/22/documentation/streams/developer-guide/
Low-Level API(低级API)
- pom依赖
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>2.2.0</version>
</dependency>
在kafka-client依赖的基础上添加以上依赖
快速入门
WordCountProcessor
package com.jiangzz.demo01;
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;
public class WordCountProcessor implements Processor<String,String> {
private ProcessorContext context;
private HashMap<String,Integer> wordPair=null;
@Override
public void init(ProcessorContext context) {
System.out.println("-----init----");
this.context=context;
WordCountTopologyDemo
wordPair=new HashMap<>();
//定时输出结果
context.schedule(Duration.ofSeconds(1), PunctuationType.WALL_CLOCK_TIME,(ts)->
{
for (Map.Entry<String, Integer> entry : wordPair.entrySet()) {
context.forward(entry.getKey(),entry.getValue());
}
});
}
@Override
public void process(String key, String value) {
String[] words = value.split("\\W+");
for (int i = 0; i < words.length; i++) {
int count=0;
if(wordPair.containsKey(words[i])){
count=wordPair.get(words[i]);
}
count+=1;
wordPair.put(words[i],count);
}
}
@Override
public void close() {
}
}
WordCountTopologyDemo
package com.jiangzz.demo01;
import org.apache.kafka.common.serialization.IntegerSerializer;
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;
public class WordCountTopologyDemo {
public static void main(String[] args) {
//0.配置KafkaStreams的连接信息
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG,"word-count-lowlevel");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
//配置默认的key序列化和反序列化
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
//1.定义计算拓扑
Topology topology=new Topology();
topology.addSource("s1","topic01");
topology.addProcessor("p1",() -> new WordCountProcessor(),"s1");
topology.addSink("sk1","topic02",
new StringSerializer(),
new IntegerSerializer(),"p1");
//3.创建KafkaStreams
KafkaStreams ka