声明: 1. 本文为我的个人复习总结, 并非那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
2. 由于是个人总结, 所以用最精简的话语来写文章
3. 若有错误不当之处, 请指出
Flink程序的组成结构:
-
Source: 数据输入
-
Transform算子: 转换算子
-
Sink: 数据输出
Flink没有Spark中类似于foreach的方法让用户进行遍历操作, 所有对外的输出操作都需要利用Sink完成
最好要有Sink(充当行动算子), 不然转换算子有时并不会触发计算
对于User<Tuple<String,String>>类型的返回值(两层泛型嵌套), 就不要用lambda简写了, 因为运行时类型被擦除了
.socketTextStream( ) 并行度一定是1
Transform 转换算子:
-
map
-
flatMap
用的是out.collect方式, 可以输出任意个参数
-
filter
-
keyBy(相当于group by, 但它是分区, 一组数据占一个分区)
- .keyBy(“itemId”) 得到的返回值K-V对中的value是<key, 元素本身>
- .keyBy(0) 按第几个字段进行分组, 等效于第一种方式
- .keyBy(t->t.getItemId( )) 得到的返回值K-V对中的value是元素本身
如果想keyBy多个字段,那就只能选择前两种方式了, .keyBy(“channel”,“behavior”)
-
shuffle
将数据打散后随机(不是按hash(key)%分区数量)分布到下游
-
split 和 select
先切分, 后选择
案例: 将温度数据拆分成high流 和 low流
SplitStream<SensorReading> splitStream = dataStream.split(new OutputSelector<SensorReading>( ) { @Override public Iterable<String> select(SensorReading value) { return (value.getTemperature( ) > 30) ? Collections.singletonList("high") : Collections.singletonList("low"); } }); DataStream<SensorReading> highTempStream = splitStream.select("high"); DataStream<SensorReading> lowTempStream = splitStream.select("low"); DataStream<SensorReading> allTempStream = splitStream.select("high", "low");
-
connect 和 CoMapFunction
先将
两个
流(数据类型可以不同
)进行混流合并
, 再分别map示例:
ConnectedStreams<Tuple2<String, Double>, SensorReading> connectedStreams = warningStream.connect(lowTempStream); DataStream<Object> resStream = connectedStreams.map(new CoMapFunction<Tuple2<String, Double>, SensorReading, Object>( ) { @Override public Object map1(Tuple2<String, Double> value) throws Exception { return new Tuple3<>(value.f0, value.f1, "warning"); } @Override public Object map2(SensorReading value) throws Exception { return new Tuple2<>(value.getId( ), "healthy"); } });
-
union
将
多个
流(数据类型得相同
)进行混流合并
参数为可变参数…
process 传入自定义的ProcessFunction
针对KeyedStream中每一个key的支流做聚合
sum
每个分区都会产生一条数据, 而不是只返回最终结果
如:
0> 1
1> 3
2> 5
3> 9
最终sum为5
sum(String field) 指定字段名称
sum(int position) 是给元组用的, position从0开始算
min
max
.max(“score”), 每条数据的score值显示目前为止遇到的最大score, 其他字段还用自己的原本的值
原数据: name score 小明 80 小红 100 小花 60 .max("score")后: name score 小明 80 小红 100 小花 100 // score被取代
minBy
maxBy
.maxBy(“score”), 每条数据的score值显示目前为止遇到的最大score, 其他字段值用 最大score的这条数据对应字段值 进行代替
原数据: name score 小明 80 小红 100 小花 60 .maxBy("score")后: name score 小明 80 小红 100 小红 100 // score被取代, name也被取代
reduce 需要先进行.keyBy, 因为如果不.keyBy就会发生全局的数据聚合, 就只能有一个reduce任务, 所以需要先.keyBy尽量优化下
函数:
-
普通函数
MapFunction, FlatMapFunction, FilterFunction, SourceFunction, SinkFunction
-
富函数
可以获取运行
环境的上下文
, 并拥有一些生命周期方法
RichMapFunction, RichFlatMapFunction, RichFilterFunction, RichSinkFunction
生命周期方法:
-
open( ) 初始化方法
-
close( ) 清理释放工作
-
getRuntimeContext( ) 得到RuntimeContext, 可以获取一些信息, 比如watemark, window, 侧输出流, 处理时间, state状态等
-
算子总结:
-
ProcessFunction可以使用
侧输出流
,生命周期方法
,状态
,注册定时事件
-
富函数可以使用
生命周期方法
,状态
-
普通函数不能使用
侧输出流
,生命周期方法
,状态
,注册定时事件
所以, 侧输出流仅有ProcessFunction可以使用
ProcessFunction是啥都能做, 是万能的底层API, 但是使用起来较麻烦
接口的继承关系上, ProcessFunction(子孙) -> RichFunction -> Function(祖先)
子类的功能对父类进行了拓展, 故子类的功能更丰富
DataStream支持的常用 Source & Sink
Source:
Kafka, Netty
Sink:
Kafka, ElasticSearch
Redis, HDFS, Hive, Flume
自定义MyBatisPlus输出到(Phoenix, ClickHouse, MySQL)
KafkaSource:
-
动态发现分区
在消费过程中, 每隔一段时间就去检查一下是否有新的分区创建
-
从kafka数据源生成watermark
kafkaSourceFunction.assignTimestampsAndWatermarks( WatermarkStrategy.forBoundedOutOfOrderness(Time.seconds(1)));
-
设置空闲等待
问题:
watermark在上下游分区间进行传递时, 可能某个上游分区始终没有数据(watermark初始为Long.MIN_VALUE), 那么下游watermark 就始终没法更新
解决:
可以使用设置withIdleness时间; 若此时间内 上游分区始终接收不到新数据, 则下游计算watermark时就不管这个上游分区了
kafkaSourceFunction.assignTimestampsAndWatermarks( WatermarkStrategy.forBoundedOutOfOrderness(Time.seconds(1)).withIdleness(Time.seconds(1)) );