Kafka Streaming
一、流计算定义
一般流式计算会与批量计算相比较。在流式计算模型中,输入是持续的,可以认为在时间上是无界的,也就意味着,永远拿不到全量数据去做计算。同时,计算结果是持续输出的,也即计算结果在时间上也是无界的。流式计算一般对实时性要求较高,同时一般是先定义目标计算,然后数据到来之后将计算逻辑应用于数据。同时为了提高计算效率,往往尽可能采用增量计算代替全量计算。批量处理模型中,一般先有全量数据集,然后定义计算逻辑,并将计算应用于全量数据。特点是全量计算,并且计算结果一次性全量输出。
二、Kafka Streams
2.1 概述
Kafka Stream是一个客户端库,用于处理和分析存储在kafka中的数据,它建立在重要的流处理概念之上。Kafka Streaming是基于Kafka的轻量级实时处理api,可以从一个topic中接收数据,进行简单处理后放入另一个topic。
从一个topic中读取数据,经过处理操作后,写入到另一个topic中
读取数据后,可以获得一个KStream对象,该对象包含了对数据集合的处理方法(类似与SparkRDD)
2.2 kafka streams的优点
简单、轻巧易部署、无缝对接Kafka、基于分区实现计算并行、基于幂等和事务特性实现精确计算、单个记录毫秒级延迟计算-实时性高、提供了两套不同风格的流处理API-(High level-Domain Specific Language|DSL开箱即用;low-level Processor API.)
2.3 Topology
Topology:表示一个流计算任务,等价于MapReduce中的job。不同的是MapReduce的job作业最终会停止,但是Topology会一直运行在内存中,除非人工关闭该Topology。
stream:它代表了一个无限的,不断更新的Record数据集。流是有序,可重放和容错的不可变数据记录序列,其中数据记录被定义为键值对。
所谓的流处理是通过Topology编织程序对stream中Record元素的处理的逻辑/流程。这种计算和早期MapReduce计算的最大差异是该计算的实时性比较高,可以满足绝大多数的实时计算场景。Kafka Stream以它的轻量级、容易部署、低延迟等特点在微服务领域相比较 专业的 Storm、spark streaming和Flink 而言有着不可替代的优势。有关Storm、SparkStreaming和Flink的内容随着课程的深入会在后续章节再展开讨论。
实例1:
导入maven项目,并导入依赖:
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
目的:将topic:in 中的数据导出到topic:out中:
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.Topology;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
public class MyStreamDemo {
public static void main(String[] args) {
// 创建Properties对象,配置Kafka Streaming配置项
Properties prop = new Properties();
prop.put(StreamsConfig.APPLICATION_ID_CONFIG, "demo");
prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.64.2:9092");
prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
// 创建流构造器
StreamsBuilder builder = new StreamsBuilder();
// 用构造好的builder将mystreamin topic 数据写入到 mystreamout topic
builder.stream("in").to("out");
// 构建 Topology 结构
Topology topology = builder.build();
final KafkaStreams kafkaStreams = new KafkaStreams(topology, prop);
// 固定的启动方式(这里就不介绍其他的启动方式了)
CountDownLatch latch = new CountDownLatch(1);
Runtime.getRuntime().addShutdownHook(new Thread("kafkaStreaming") {
@Override
public void run() {
kafkaStreams.close();
latch.countDown();
}
});
kafkaStreams.start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
}
实例2:
实现一个计算数字总和,从一个topic传入数值,从另一个topic得到累计的数值
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
public class SumStreamDemo {
public static void main(String[] args) {
Properties prop = new Properties();
prop.put(StreamsConfig.APPLICATION_ID_CONFIG, "sum");
prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "num01:9092");
prop.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 3000);
prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
StreamsBuilder builder = new StreamsBuilder();
builder.stream("sumIn") // 从sumin主题获取数据
.map((key, value) -> new KeyValue<>("sum", value.toString())) // 因为上面指定的是String类型的value值,所以将数值转为String类型
.groupByKey()
.reduce((value1, value2) -> { // 数值相加功能,顺带输出下中间计算的结果,逻辑和spark的reduce功能完全相同
int sum = Integer.valueOf(value1) + Integer.valueOf(value2);
System.out.println(Integer.valueOf(value1) + "+" + Integer.valueOf(value2) + " = " + sum);
return Integer.toString(sum);
})
.toStream().to("sumOut"); // 重新转为stream后输出到sumout主题
Topology topology = builder.build();
final KafkaStreams kafkaStreams = new KafkaStreams(topology, prop);
CountDownLatch latch = new CountDownLatch(1);
Runtime.getRuntime().addShutdownHook(new Thread("stream") {
@Override
public void run() {
kafkaStreams.close();
latch.countDown();
}
});
kafkaStreams.start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
}
三、窗口
3.1 Hopping time window
跳跃时间窗口,这类窗口需要传入两个参数:size和advance interval,分别代表窗口的宽度和创建创建窗口的时间间隔。每经过一个advance interval就会自动创建一个新的窗口。
窗口遵循左闭右开的原则
3.1 Tumbling time window
滚动时间窗口,是跳跃时间窗口的一种特例,当跳跃时间窗口的size和advance iterval值相等时,它就变成了滚动时间窗口。
滚动时间窗口只有一个参数:size,表示窗口的尺寸,一个窗口的结束点会是下一个窗口的起始点。窗口之间没有间隙,也不重叠。
size = 5min的滚动时间窗口示意图:
3.1 Session window
该会话窗口只需要传入一个参数:gap(超时时间,即一个会话窗口超过这个时间间隔,该窗口就会关闭),按key分组,相同的key是一个会话,即使一个会话没有结束,重新获得了另一个key,就会开启另一个会话窗口。