Flink
运行的方式
-
BATCH:批处理
-
底层基于MR模型
-
只能用于处理有界的数据
-
-
STREAM流处理
- 1、底层基于持续流模型
- 2、既能处理无界流也可以用于有界流
-
流批合一
- 在Flink中同一套DataStream的API既可以用作流处理也可以用作批处理
// 切分每一行单词 DataStream<String> wordsDS = lineDS.flatMap((line, collector) -> { String[] splits = line.split(","); // 遍历每一个单词 for (String word : splits) { // 使用collector将结果数据发送给下游 collector.collect(word); } // java中的lambda表达式有时无法自动推断类型,需要手动指定 }, Types.STRING);
sink
DataStream<Tuple2<String, Integer>> clazzCnt = studentDS
.map(line -> Tuple2.of(line.split(",")[4], 1), Types.TUPLE(Types.STRING, Types.INT))
.keyBy(kv -> kv.f0)
.sum(1);
// clazzCnt.print(); // 本质上也是一种sink,sink到console
FileSource
读取文件的方式
- 指定一个格式的方式
- 指定一个路径
- 通过monitorContinuously来实现对目录的实时监控,将读文件转换无界流进行处理
FileSource<String> fileSource = FileSource.forRecordStreamFormat(
new TextLineInputFormat()// 指定读取文件的格式
, new Path("flink/data/word/")
)
.monitorContinuously(Duration.ofMillis(5)) // 指定时间间隔监控目录的变化
.build();
// 使用FileSource
DataStream<String> wordDS2 = env.fromSource(fileSource, WatermarkStrategy.noWatermarks(), "fileSource");
Socket Source
一般用于代码调试及开发
DataStream<String> socketDS = env.socketTextStream("master", 8888);
CollectionSource
基于本地集合的Source
DataStream<String> arrDS = env.fromCollection(arr);
Map
传入一条数据返回一条数据
// 传入处理的逻辑三种方式:
// 1、使用lambda表示式
studentDS.map(stu -> stu.split(",")[1], Types.STRING).print();
// 2、使用匿名内部类
studentDS.map(new MapFunction<String, String>() {
@Override
public String map(String value) throws Exception {
return value.split(",")[1];
}
}).print();
// 3、使用自定义Function类
studentDS.map(new MyMapFunction()).print();
studentDS.map(new NameMapFunction()).print();
// 通过flatMap提取name
studentDS.flatMap((stu, out) -> {
out.collect(stu.split(",")[1]);
}, Types.STRING).print();
env.execute();
}
}
// 自定义类实现Function接口
class MyMapFunction implements MapFunction<String, String> {
@Override
public String map(String value) throws Exception {
return value.split(",")[1];
}
}
FlatMap
传入一条数据返回N条数据
arrDS.flatMap(new FlatMapFunction<String, String>() {
/**
* 传入一条数据flatMap方法就会执行一次
*/
@Override
public void flatMap(String value, Collector<String> out) throws Exception {
for (String word : value.split(",")) {
out.collect(word);
}
}
}).print();
filter
用于过滤,根据方法返回布尔值进行过滤
返回True则保留数据
返回False则返回过滤数据
DataStream<String> clazz = studentDS
.filter(new FilterFunction<String>() {
@Override
public boolean filter(String value) throws Exception {
// 分别提取班级以及性别
String[] splits = value.split(",");
String clazz = splits[4];
String gender = splits[3];
// System.out.println(clazz);
// System.out.println(gender);
return clazz.startsWith("文科") && "女".equals(gender);
}
});
Flink中的算子不是懒执行的,不需要action算子触发
统一由evn.execute()触发Flink任务的执行
keyby
进行流上的分组
让相同key的数据能够进入同一个线程对应的Task中进行处理
同一个Task中也会有不同的key
DataStream<Tuple2<String, Integer>> kvDS = lineDS.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) throws Exception {
for (String word : value.split(",")) {
out.collect(Tuple2.of(word, 1));
}
}
});
按照Key进行分组,默认使用Hash分组的原理
KeyedStream同样也为DataStream的子类
它是在DS的基础之上增加了聚合类一些操作,例如:max、min、sum等
Ruduce
将数据切分,一个单词变成一行数据并转化成KV格式
DataStream<Tuple2<String, Integer>> sumDS = grpDS.reduce(new ReduceFunction<Tuple2<String, Integer>>() {
/**
* 如果第一条数据进来,则不会触发reduce方法的执行
* 如果第两条数据进来,则一条赋给value1,另一条赋给value2,就会执行reduce方法,返回一个计算的结果
* 如果第三条数据进来,则上一次的计算结果会赋给value1,进来的这条数据赋给value2,触发reduce方法执行并计算新的结果
* 以此类推
*
* 由于会使用上一次的计算结果,故这是一个有状态的操作
* @param value1
* @param value2
* @return
* @throws Exception
*/
@Override
public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1, Tuple2<String, Integer> value2) throws Exception {
System.out.println(value1);
System.out.println(value2);
// 实现累加的聚合操作
return Tuple2.of(value1.f0, value1.f1 + value2.f1);
}
});
union
将多个DS合并成一个DS
ArrayList<String> arr = new ArrayList<>();
arr.add("java,scala,python");
DataStream<String> arrDS01 = env.fromCollection(arr);
DataStream<String> arrDS02 = env.fromCollection(arr);
DataStream<String> unionDS = arrDS01.union(arrDS02);
proctime
间隔N秒统计单词的数量,需要使用滚动窗口
kvDS.keyBy(kv -> kv.f0) .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
.sum(1)
.print();
每隔ns统计最近10s内的单词数量
需要使用滑动窗口
适用榜单的统计
需要指定两个时间:窗口的大小、滑动的时间
当窗口大小 等于 滑动时间时,可以视为滚动窗口
当窗口大小 大于 滑动时间时,可以视为滑动窗口
当然没有窗口大小 小于 滑动时间 这种情况
kvDS.keyBy(kv -> kv.f0) .window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)))
.sum(1)
.print();
env.execute();
eventTime
当并行度不为1时,Task的数量会大于1的,所以相当于有多个Task在并行执行
所以Task与Task之间也是需要进行水位线对齐的
将并行度设为1,那就不会存在Task之间水位线对不齐的问题,因为只有一个Task
在真实的场景中,由于数据量是远超过并行度的,所以Task之间对其水位线是相对更容易的
DataStream<Tuple2<String, Long>> assDS = wordTimeDS
.assignTimestampsAndWatermarks(
WatermarkStrategy
// 1、使用数据中最大的时间作为水位线
// .<Tuple2<String, Long>>forMonotonousTimestamps()
// 2、将水位线前移5s,解决数据延时到达的问题
// 前移的太多就会导致整体任务延时较大
.<Tuple2<String, Long>>forBoundedOutOfOrderness(Duration.ofSeconds(5))
// 告诉flink,数据中哪部分作为时间戳,即使用事件时间
.withTimestampAssigner(TimestampAssignerSupplier.of((kv, ts) -> kv.f1)
));
Window
Flink的窗口分为三类:
1、基于时间的窗口
基于事件时间:
滑动:SlidingEventTimeWindows
滚动:TumblingEventTimeWindows
基于处理时间:
滑动:SlidingProcessingTimeWindows
滚动:TumblingProcessingTimeWindows
2、基于会话的窗口
基于事件时间:EventTimeSessionWindows
基于处理时间:ProcessingTimeSessionWindows
3、基于计数的窗口:可以直接在KeyBy之后直接.出来
滑动:
滚动: