说明
通过本案例,理解 ProcessFunction 函数的功能,可以获取时间戳、当前水位线,可以更好的理解 watermark 的生成方式
提示
为了演示效果
ProcessFunction的实现类,只是打印 数据、处理时间、水位线,没有对数据做额外处理,直接将数据返回了将并行度设置为1或很小的数字。因为每个子任务在第一次处理数据时,watermark初始值都是很小的负数。
如:我的CPU是4核8线程,如果不设置并行度,就是8个子任务同时执行,那么处理前8个数据时,watermark就是很小的负数由 flink1.12.x 水位线生成时机、分配器(Periodic Watermarks周期性分配水位线、Punctuated Watermarks定点水位线) 可知,每间隔 200ms 发出一次watermark,所以休眠200ms以上,否则打印的水位线都是负数
这里为了便于理解,延迟时间为
0这里为了便于理解,没有使用 window窗口
数据文件
在工程中创建 data 目录,然后创建 2.csv 文件,内容如下:
java从入门到精通,50,李雷,1
hadoop从入门到精通,80,李雷,2
flink从入门到精通,70,韩梅梅,4
mysql从入门到精通,100,李雷,15
hbase从入门到精通,50,李雷,6
hive从入门到精通,100,韩梅梅,3
html从入门到精通,120,韩梅梅,13
hbase从入门到精通,50,李雷,7
hive从入门到精通,100,韩梅梅,8
html从入门到精通,120,韩梅梅,9
解释:
- 第一列:书名
- 第二列:价格
- 第三列:买家姓名
- 第四列:订单时间,这里为了简单好理解,采用的是 时间戳,即:当前时间 距离
1970-01-01 00:00:00的毫秒数
java
package processfunction;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple4;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.util.Collector;
import java.time.Duration;
public class StdProcessFunction {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
/*
必须将并行度设置为1或很小的数字
因为每个子任务在第一次处理数据时,watermark初始值都是很小的负数
我的CPU是4核8线程,如果不设置并行度,就是8个子任务同时执行,那么处理前8个数据时,watermark就是很小的负数
*/
env.setParallelism(1);
DataStream<String> ds = env.readTextFile("data/2.csv");
SingleOutputStreamOperator<Tuple4<String,Double,String, Long>> soso = ds.flatMap(new FlatMapFunction<String, Tuple4<String,Double,String, Long>>() {
@Override
public void flatMap(String value, Collector<Tuple4<String,Double,String, Long>> out) throws Exception {
String[] arr = value.split(",");
Double price = Double.parseDouble(arr[1]);
Long timestamp = Long.parseLong(arr[3]) ;
Tuple4 data = Tuple4.of(arr[0],price,arr[2],timestamp);
out.collect(data);
// 需要休眠一会,水位线执行没那么快,否则都是负数
Thread.sleep(210);
}
});
SingleOutputStreamOperator<Tuple4<String, Double, String, Long>> watermarksSOSO = soso
.assignTimestampsAndWatermarks(
// 这里为了简单,延迟时间为 0
WatermarkStrategy.<Tuple4<String, Double, String, Long>>forBoundedOutOfOrderness(Duration.ZERO)
.withTimestampAssigner(new SerializableTimestampAssigner<Tuple4<String, Double, String, Long>>() {
@Override
public long extractTimestamp(Tuple4<String, Double, String, Long> element, long recordTimestamp) {
return element.f3;
}
})
);
SingleOutputStreamOperator<Tuple4<String, Double, String, Long>> processSOSO = watermarksSOSO.process(new ProcessFunction<Tuple4<String, Double, String, Long>, Tuple4<String, Double, String, Long>>() {
@Override
public void processElement(Tuple4<String, Double, String, Long> value, Context ctx, Collector<Tuple4<String, Double, String, Long>> out) throws Exception {
Long timestamp = ctx.timestamp(); // 处理时间
long watermark = ctx.timerService().currentWatermark();
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
String s = String.format("%s,timestamp:%s,watermark:%s,indexOfThisSubtask:%s",value, timestamp, watermark, indexOfThisSubtask);
System.out.println(s);
//直接将源数据
out.collect(value);
}
});
// processSOSO.print();
env.execute();
}
}
执行结果
(java从入门到精通,50.0,李雷,1),timestamp:1,watermark:-9223372036854775808,indexOfThisSubtask:0
(hadoop从入门到精通,80.0,李雷,2),timestamp:2,watermark:0,indexOfThisSubtask:0
(flink从入门到精通,70.0,韩梅梅,4),timestamp:4,watermark:1,indexOfThisSubtask:0
(mysql从入门到精通,100.0,李雷,15),timestamp:15,watermark:3,indexOfThisSubtask:0
(hbase从入门到精通,50.0,李雷,6),timestamp:6,watermark:14,indexOfThisSubtask:0
(hive从入门到精通,100.0,韩梅梅,3),timestamp:3,watermark:14,indexOfThisSubtask:0
(html从入门到精通,120.0,韩梅梅,13),timestamp:13,watermark:14,indexOfThisSubtask:0
(hbase从入门到精通,50.0,李雷,7),timestamp:7,watermark:14,indexOfThisSubtask:0
(hive从入门到精通,100.0,韩梅梅,8),timestamp:8,watermark:14,indexOfThisSubtask:0
(html从入门到精通,120.0,韩梅梅,9),timestamp:9,watermark:14,indexOfThisSubtask:0
解释:
- 收到第一个数据
java从入门到精通,50,李雷,1,watermark是负数,即:初始值 - 收到第二个数据
hadoop从入门到精通,80,李雷,2,watermark才开始计算,是0,计算过程:从 flink1.12.x watermark 源码分析 的output.emitWatermark(new Watermark(maxTimestamp - outOfOrdernessMillis - 1));可知,maxTimestamp(上一个数据的时间戳,即:1) - outOfOrdernessMillis(延迟时间,这里设置为0) - 1就是1 - 0 - 1 - 收到第三个数据
flink从入门到精通,70,韩梅梅,4,watermark是1,计算过程:maxTimestamp(上一个数据的时间戳,即:2) - outOfOrdernessMillis(延迟时间,这里设置为0) - 1就是2 - 0 - 1 = 1 - 收到第四个数据
mysql从入门到精通,100,李雷,15,watermark是3,计算过程:maxTimestamp(上一个数据的时间戳,即:4) - outOfOrdernessMillis(延迟时间,这里设置为0) - 1就是4 - 0 - 1 = 3 - 收到第五个数据
hbase从入门到精通,50,李雷,6,watermark是14,计算过程:maxTimestamp(上一个数据的时间戳,即:15) - outOfOrdernessMillis(延迟时间,这里设置为0) - 1就是15 - 0 - 1 = 14 - 收到后面的数据,从 flink1.12.x watermark 源码分析 的
maxTimestamp = Math.max(maxTimestamp, eventTimestamp);可知,将 最大时间戳 与 当前数据的 事件时间 做比较,取出最大值。由于当前最大的时间就是15,所以一直都是用15计算 watermark,所以一直是14