文章目录
1、时间语义

| 时间语义 | 直译 | 说明 |
|---|---|---|
| Event Time | 时间时间 | 事件发生的时间 |
| Process Time | 处理时间 | 事件发生后,进入到程序(窗口) |
事 件 时 间 < 处 理 时 间 事件时间<处理时间 事件时间<处理时间
2、Watermark(水位线)
- Watermark,可译为水位线,用来标记水流的事件时间进度
- Watermark作为数据流的一部分在流动,并且携带一个时间戳
t
Watermark(t)表示流的事件时间已经到达t,后续数据的事件时间应当大于t
有序流中的水印
乱序流中的水印
W(t)表示流的事件时间已经到达t,后续不应出现时间时间小于t的数据
2.1、Watermark策略

2.2、Watermark与窗口
- WaterMark触发窗口计算
- 窗口允许迟到的数据
- 侧输出流(sideOutput):处理关窗之后到达的数据

- W a t e r M a r k ≥ 窗 口 结 束 时 间 WaterMark \ge 窗口结束时间 WaterMark≥窗口结束时间 ,会触发窗口计算
- 窗 口 结 束 时 间 < W a t e r M a r k < ( 窗 口 结 束 时 间 + 允 许 迟 到 时 间 ) 窗口结束时间 \lt WaterMark \lt (窗口结束时间 + 允许迟到时间) 窗口结束时间<WaterMark<(窗口结束时间+允许迟到时间),每进一条迟到的数据就计算一次
- W a t e r M a r k ≥ ( 窗 口 结 束 时 间 + 允 许 迟 到 时 间 ) WaterMark \ge (窗口结束时间 + 允许迟到时间) WaterMark≥(窗口结束时间+允许迟到时间) ,会关闭窗口
2.3、多并行度下Watermark的更新和传递
从所有分区中取事件时间最小的WaterMark来广播WaterMark
3、Watermark代码
本地开发环境:WIN10+IDEA
3.1、pom.xml
<!-- 配置 -->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<flink.version>1.14.6</flink.version>
<scala.binary.version>2.12</scala.binary.version>
<slf4j.version>2.0.3</slf4j.version>
<log4j.version>2.17.2</log4j.version>
</properties>
<!-- https://mvnrepository.com/ -->
<dependencies>
<!-- Flink -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-runtime-web_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
3.2、log4j.properties
log4j.rootLogger=error, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
3.3、java
-
Watermark策略
-
forMonotonousTimestamps()是时间单调递增的水位线策略
相当于forBoundedOutOfOrderness(Duration.ofSeconds(0))
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.streaming.api.functions.windowing.ProcessAllWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import java.time.Duration;
import java.util.Scanner;
public class Hello {
public static void main(String[] args) throws Exception {
//创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//设置并行度
env.setParallelism(1);
//Watermark策略(最大事件时间-5秒)
WatermarkStrategy<Long> w = WatermarkStrategy
.<Long>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((SerializableTimestampAssigner<Long>) (s, l) -> s * 1000L);
//加入自定义数据源
DataStreamSource<String> dss = env.addSource(new MySource());
//################################### 逻辑 ########################################
dss
//String转Long
.map(Long::valueOf)
//确定时间水位线
.assignTimestampsAndWatermarks(w)
//事件时间滚动窗口
.windowAll(TumblingEventTimeWindows.of(Time.seconds(1000)))
//允许迟到
.allowedLateness(Time.seconds(1))
//窗口内数据处理
.process(new ProcessAllWindowFunction<Long, String, TimeWindow>() {
@Override
public void process(Context context, Iterable<Long> in, Collector<String> out) {
//打印窗口范围
System.out.println(context.window().toString());
//在窗口内,收集元素
out.collect(String.valueOf(in));
}
})
//打印结果
.print();
//################################### 逻辑 ########################################
env.execute();
}
public static class MySource implements SourceFunction<String> {
public MySource() {}
@Override
public void run(SourceContext<String> sc) {
Scanner scanner = new Scanner(System.in);
while (true) {
String str = scanner.nextLine().trim();
if (str.equals("STOP")) {break;}
if (!str.equals("")) {sc.collect(str);}
}
scanner.close();
}
@Override
public void cancel() {}
}
}
3.4、测试结果
窗口大小1000秒,Watermark=事件时间-5秒,允许迟到1秒
绿色为秒数输入,白色为窗口结果输出
4、多并行度Watermark代码
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import java.time.Duration;
import java.util.Scanner;
public class Hello {
public static void main(String[] args) throws Exception {
//创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//设置并行度
env.setParallelism(2);
//Watermark策略
WatermarkStrategy<Long> w = WatermarkStrategy
.<Long>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((SerializableTimestampAssigner<Long>) (s, l) -> s * 1000L);
//加入自定义数据源
DataStreamSource<String> dss = env.addSource(new MySource());
//################################### 业务逻辑 ########################################
dss
//String转Long
.map(Long::valueOf)
//确定时间水位线
.assignTimestampsAndWatermarks(w)
//分区
.keyBy(s -> s % 2)
//事件时间滚动窗口
.window(TumblingEventTimeWindows.of(Time.seconds(1000)))
//窗口内数据处理
.process(new ProcessWindowFunction<Long, String, Long, TimeWindow>() {
@Override
public void process(Long key, Context context, Iterable<Long> in, Collector<String> out) {
//打印当前分区和水位线
System.out.println("分区:" + key + ";水位线:" + context.currentWatermark());
//打印窗口范围
System.out.println(context.window().toString());
//在窗口内,收集元素
out.collect(String.valueOf(in));
}
})
//打印
.print("输出");
//################################### 业务逻辑 ########################################
env.execute();
}
public static class MySource implements SourceFunction<String> {
public MySource() {}
@Override
public void run(SourceContext<String> sc) {
Scanner scanner = new Scanner(System.in);
while (true) {
String str = scanner.nextLine().trim();
if (str.equals("STOP")) {break;}
if (!str.equals("")) {sc.collect(str);}
}
scanner.close();
}
@Override
public void cancel() {}
}
}
测试结果(并行度=2)
5、Appendix
| en | 🔉 | cn |
|---|---|---|
| watermark | ˈwɔːtərmɑːrk | n. 水印;水位标志;vt. 在……上印水印(图案) |
| gap | ɡæp | n. 缝隙,缺口;(时间上的)间隔 |
| tumbling | ˈtʌmblɪŋ | n. 翻腾运动 |
| tumble | ˈtʌmbl | v. 翻滚;n. 跌倒,滚落;(数量、价值)暴跌;混乱,杂乱;翻跟头 |
| in order | 按顺序 | |
| out of order | 次序颠倒 | |
| bounded | baʊndɪd | adj. 有界限的 |
| assign | əˈsaɪn | v. 布置(任务);分配(某物);派遣;确定(价值、功能、时间、地点) |
| monotonous | məˈnɑːtənəs | adj. 单调乏味的 |
本文介绍了Flink中时间语义的概念,重点讲解了Watermark的原理及其在乱序数据流中的作用。Watermark策略用于处理事件时间窗口,允许迟到的数据并确保最终的一致性。通过示例代码展示了如何设置和使用Watermark策略,并讨论了多并行度下Watermark的更新和传递。此外,还提供了本地开发环境的配置和测试结果。





1280

被折叠的 条评论
为什么被折叠?



