本文开头附:Flink 学习路线系列 ^ _ ^
结合上一篇博文: Flink 中 Time 和 Window 介绍,我们了解到 Flink 的流式处理中,会涉及到 3 个时间概念。分别是:Event Time
、Ingestion Time
、Processing Time
。
如果你还对这三个时间概念不了解,建议你跳转上一篇博文了解一下。通过上文,我们也已经知道了EventTime
对于业务来说是最有意义的时间。接下来我们就分析一下 EventTime 在并行Source
和非并行Source
中的使用情况。
如下是一段 log 日志,我们根据该日志格式,来分析客户的下单量情况。
1581490623000,James,5
1581490624150,John,2
…
接下来,我们从并行Source
和 非并行Source
两个方向,来使用 EventTime 处理实时数据。(接下来示例,设置延迟为0s,即不延迟)
非并行Source,以 socketTextStream为例来介绍 Flink使用 EventTime 处理实时数据。
/**
* TODO 非并行Source EventTime
*
* @author liuzebiao
* @Date 2020-2-12 15:25
*/
public class EventTimeDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//设置EventTime作为时间标准
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
//读取source,并指定(1581490623000,Mary,3)中哪个字段为EventTime时间
//WaterMarks:是Flink中窗口延迟触发的机制。Time.seconds(0)表示无延迟。
SingleOutputStreamOperator<String> source = env.socketTextStream("localhost", 8888).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(0)) {
@Override
public long extractTimestamp(String line) {
String[] split = line.split(",");
return Long.parseLong(split[0]);
}
});
SingleOutputStreamOperator<Tuple2<String, Integer>> mapOperator = source.map(line -> {
String[] split = line.split(",");
return Tuple2.of(split[1], Integer.parseInt(split[2]));
}).returns(Types.TUPLE(Types.STRING,Types.INT));
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = mapOperator.keyBy(0);
//EventTime滚动窗口
WindowedStream<Tuple2<String, Integer>, Tuple, TimeWindow> windowedStream = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));
SingleOutputStreamOperator<Tuple2<String, Integer>> sum = windowedStream.sum(1);
sum.print();
env.execute("EventTimeDemo");
}
}
备注: (158149062
3000
转换后为:2020-02-12 14:57:03
1581490624000
转换后为:2020-02-12 14:57:04)
当我们在Socket中输入如下数据:
1581490623000
,Mary,2
1581490624000
,John,3
1581490624500
,Clerk,1
1581490624998
,Maria,4
1581490624999
,Mary,3
1581490626000
,Mary,3
1581490630800
,Steve,3 (2020-02-12 14:57:10.800)窗口定义的时间是:含头不含尾。即:[0,5),
图片解析:(我们定义滚动窗口为5s,我们分析图片发现到4998时,并没有输出内容。因为4998还没超过5s,窗口规定是>=临界值时触发,所以当我们输入4999临界时,我们发现输出内容了,说明一个窗口滚动完成,输出内容包含4999这个时间的值;当输入6000时,6000在[5,10)之间没有>10,所以不输出。输入30800【2020-02-12 14:57:10.800)】,已经超过10s,所以结果只输出1个 (Mary,3),因为Steve已经被分到另一个窗口了)还有一个问题,就是:当输入到 4999 时,只是Mary这个分组满足5s这个条件,但是其它分组John,Clerk 等也同步输出结果了。显然这不符合逻辑。为什么会出现这种情况呢?是因为SocketStream 是非并行数据流,所以才会出现这样子的结果。(接下来我们就是用并行数据流KafkaSource来分析)
并行Source,以 KafkaSouce 为例来介绍 Flink使用 EventTime 处理实时数据。
并行KafkaSource EventTime示例(读取 topic为 window_demo中的消息),代码如下所示:
/**
* TODO 并行KafkaSource EventTime示例(读取 topic为 window_demo中的消息)
*
* @author liuzebiao
* @Date 2020-2-12 15:25
*/
public class EventTimeDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//设置EventTime作为时间标准
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
//Kafka props
Properties properties = new Properties();
//指定Kafka的Broker地址
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.204.210:9092,192.168.204.211:9092,192.168.204.212:9092");
//指定组ID
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "flinkDemoGroup");
//如果没有记录偏移量,第一次从最开始消费
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
FlinkKafkaConsumer<String> kafkaSource = new FlinkKafkaConsumer("window_demo", new SimpleStringSchema(), properties);
//2.通过addSource()方式,创建 Kafka DataStream
//读取source,并指定(1581490623000,Mary,3)中哪个字段为EventTime时间
SingleOutputStreamOperator<String> source = env.addSource(kafkaSource).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(0)) {
@Override
public long extractTimestamp(String line) {
String[] split = line.split(",");
return Long.parseLong(split[0]);
}
});
SingleOutputStreamOperator<Tuple2<String, Integer>> mapOperator = source.map(line -> {
String[] split = line.split(",");
return Tuple2.of(split[1], Integer.parseInt(split[2]));
}).returns(Types.TUPLE(Types.STRING,Types.INT));
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = mapOperator.keyBy(0);
//EventTime滚动窗口
WindowedStream<Tuple2<String, Integer>, Tuple, TimeWindow> windowedStream = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));
SingleOutputStreamOperator<Tuple2<String, Integer>> sum = windowedStream.sum(1);
sum.print();
env.execute("EventTimeDemo");
}
}
2.1.1 创建 Topic 命令如下:
bin/kafka-topics.sh --create
--zookeeper 192.168.204.210:2181,192.168.204.211:2181,192.168.204.212:2181 --replication-factor 1 --partitions 3
--topic window_demo
(特别注意一下:此处创建了3个分区)
2.1.2 创建 Topic 成功截图(点击放大查看):
2.1.3 使用命令,写入数据到Kafka:
bin/kafka-console-producer.sh
--broker-list 192.168.204.210:9092 --topic window_demo
使用命令写入以下数据:
1581490623000,Mary,2
1581490624000,John,3
1581490624500,Clerk,1
1581490624998,Maria,4
1581490624999,Mary,3
在1.并行Source
一例中,当我们输入1581490624999,Mary,3
时,我们看到控制台会直接帮我们输出计算结果。
但是,在使用 KafkaSource 时,我们连续输入了 3次1581490624999,Mary,3
,我们才看到控制台帮我们输出计算了结果。
那这是为什么呢?这是 并行Source 和 非并行Source 的原因导致的(这里涉及到 KafkaSource 创建的 topic,有 3 个分区的原因,如下图所示)
博主写作不易,来个关注呗
求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙
博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ