flink

1 简述

一个 Flink 程序,其实就是对 DataStream 的各种转换。

1.1 重要的类

TypeInformation

SourceFunction

StreamExecutionEnvironment

DataStream

SingleOutputStreamOperator (继承DataStream,代表转化结果的流)

KeyedStream (继承dataStream,再此基础上增加了聚合功能)

WindowStream (keyedStream .window() --> WindowStream )

AllWindowedStream (DataStream. window() --> AllWindowStream)

BroadcastConnectedStream (广播连接流,普通流和广播流进行连接合并后形成广播连接流)

ConnectedStreams (连接流, 两个普通流连接形成,一国两制处理)

JoinedStreams (stream1 .join(stream2 ) 形成 需要借用 where和 .equalTo 定义连接条件)

  • 窗口数据流之间的转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Od6WS4E-1658918023645)(C:\Users\gckj\AppData\Roaming\Typora\typora-user-images\image-20220720151123612.png)]

flink重点

  • datastream api中常用的算子
  • 窗口 (Window)、
  • 多流转换、
  • 底层的处理函数(Process Function)
  • 状态编程等更加高级的用法。
  • flink sql

建议:学习完了自己画个思维导图整理下,都学了那些知识,那些没掌握;复盘一下

参考资料

DataStream的设计与实现 - 掘金 (juejin.cn)

[What is the idea behind SingleOutputStreamOperator extending from DataStream class in Apache Flink? - Stack Overflow](https://stackoverflow.com/questions/53638145/what-is-the-idea-behind-singleoutputstreamoperator-extending-from-datastream-cla#:~:text=SingleOutputStreamOperator represents a user defined transformation applied on,but it is the result of a transformation.)

2 api

2 dataStream api

2.1 基本转换算子

  • map
  • filter
  • flatmap

2.2 聚合算子

  • keyBy 只是一个流的分区操作,并不是一个转换算子

keyby之后的计算

keyBy 通过指定键(key),可以将一条流从逻辑 上划分成不同的分区(partitions)。这里所说的分区,其实就是并行处理的子任务,也就对应 着任务槽(task slot)

所有具有相同的 key 的数据,都将被发往同一个分区,那么下一步算子操作就将会在同一个 slot 中进行处理了。

3 UDF

3.1 函数类(Function Classes

3.2 匿名函数(Lambda)

3.3 富函数类(Rich Function Classes)

dataStream 的增强版本 ;RichxxFunction

富函数类可以获取运行环境的上下文,并拥有一些生命周期方法,

4 物理分区

  • 物理分区与 keyBy 另一大区别在于,keyBy 之后得到的是一个 KeyedStream,而物理分 区之后结果仍是 DataStream,且流中元素数据类型保持不变。从这一点也可以看出,分区算子 并不对数据进行转换处理,只是定义了数据的传输方式。
  • 常见的物理分区策略有随机分配(Random)、轮询分配(Round-Robin)、重缩放(Rescale) 和广播(Broadcast)

1 随机分区

stream.shuffle()

2 轮询分配

stream.rebalance()

3 重缩放

stream.rescale()

一种优化的策略

4 广播

stream. broadcast()

5 全局分区 (一般用不到)


6 自定义分区

stream.partitionCustom()


5 sink

1 连接到外部系统 (见官网 和 api)

2 输出到文件

简单方法(最后的并行都设置为1、无检查点、富函数 ) datastream.writeAsXX()

StreamingFileSink (专用、有富函数、检查点、一致性)

原理方法:它的主要操作是将数据写入桶(buckets),每个桶中的数据都可以分割成一个个大小有限的分 区文件,这样一来就实现真正意义上的分布式文件存储

3 SinkFunction、 RichSinkDunction

6 时间和窗口

1 时间分类:

事件时间:数据产生的时间,(Event Time);在 Flink 中把它叫作事件时间的“水位线”(Watermarks)

处理时间:数据真正被处理的时刻,(Processing Time)

2 水位线:

时间戳属于那个窗口

窗口的开启和关闭

窗口关闭和触发计算

3 窗口api

分为按键分区和非按键分区,调用方法有所不同


stream.keyBy(...)
 .window(...)
stream.windowAll(...)  #windowAll 是一个非并行的操作

窗口操作主要有两部分,窗口分配器,窗口函数, (使用什么窗口,每个窗口做什么操作)

stream.keyBy(<key selector>)
 .window(<window assigner>)
 .aggregate(<window function>

3.1 窗口分配器

  • 按照驱动类型来划分:时间窗口(3)、计数窗口(2)、全局窗口(1)。api主要是时间和技术窗口


  • 时间窗口是最常用的窗口类型,又可以细分为滚动、滑动和会话三种。
2 x 3 = 6 

#处理时间
(1)滚动处理时间窗口
stream.keyBy(...)
.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
.aggregate(...)
(2)滑动处理时间窗口  
stream.keyBy(...)
.window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)))
.aggregate(...)

(3)处理时间会话窗口
stream.keyBy(...)
.window(ProcessingTimeSessionWindows.withGap(Time.seconds(10)))
.aggregate(...)

#事件时间
(4)滚动事件时间窗口
stream.keyBy(...)
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.aggregate(...)

(5)滑动事件时间窗口
stream.keyBy(...)
.window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)))
.aggregate(...)

(6)事件时间会话窗口
stream.keyBy(...)
.window(EventTimeSessionWindows.withGap(Time.seconds(10)))
.aggregate(...)
  • 计数窗口:滚动计数窗口和滑动计数窗口两类(没详细看,需要再看)
stream.keyBy(...)
.countWindow(10)

stream.keyBy(...)
.countWindow(10,3)
  • 全局窗口
stream.keyBy(...)
.window(GlobalWindows.create());

3.2 窗口函数

窗口函数分两类:增量聚合函数、全窗口函数

3.2.1 增量聚合函数

典型的增量聚合函数有两个:ReduceFunction 和 AggregateFunction。

  • 规约函数(特点)
    利用状态、两两聚合、
    聚合状态的类型、输出结果的类型、输入数据类型 ;三者一致
  • 聚合函数 (特点)
    创建累加器、先利用累加器聚合、再从累加器中提取聚合结果、最后合并两个累加器结果;(累加器利用了状态)
    取消类型一致的限制,让输入数据、中间状态、输出结果三者类 型都可以不同

3.2.2 全窗口函数

与增量聚合函数不同,全窗口函数需要先收集窗 口中的数据,并在内部缓存起来,等到窗口要输出结果的时候再取出数据进行计算

全窗口函数有两种:窗口函数、处理窗口函数 (建议使用处理函数窗口、功能覆盖窗口函数)

  • 窗口函数
# 建议使用处理窗口函数
stream
 .keyBy(<key selector>)
 .window(<window assigner>)
 .apply(new MyWindowFunction())
  • 处理窗口函数
# 
stream
 .keyBy(<key selector>)
 .window(<window assigner>)
 .process(new UvCountByWindow())
  • 增量聚合和全窗口函数的结合使用

见6.3.1 聚合函数的第二个参数,传入全窗口函数(2个)

窗口处理的主体还是增量聚合,而引入全窗口函数又可以获取到更多的信息包装输出,这 样的结合兼具了两种窗口函数的优势,在保证处理性能和实时性的同时支持了更加丰富的应用场景

  • 其他api
    1 )触发器:控制窗口什么时候触发计算
stream.keyBy(...)
 .window(...)
 .trigger(new MyTrigger())

2)移除器:

再窗口触发计算之前或之后进行操作

stream.keyBy(...)
 .window(...)
 .evictor(new MyEvictor())

3)允许迟到

stream.keyBy(...)
 .window(TumblingEventTimeWindows.of(Time.hours(1)))
 .allowedLateness(Time.minutes(1))

4) 将迟到的数据放入侧输出流

定义:定义测输出流标签 new OutputTag(“late”) {};

放:sideOutputLateData(outputTag)

取:getSideOutput(outputTag);

注意:getSideOutput()是 SingleOutputStreamOperator 的方法,获取到的侧输出流数据 类型应该和 OutputTag 指定的类型一致,与窗口聚合之后流中的数据类型可以不同。

# 放测输出流  
OutputTag<Event> outputTag = new OutputTag<Event>("late") {};
stream.keyBy(...)
 .window(TumblingEventTimeWindows.of(Time.hours(1)))
.sideOutputLateData(outputTag)
# 取测输出流
SingleOutputStreamOperator<AggResult> winAggStream = stream.keyBy(...)
 .window(TumblingEventTimeWindows.of(Time.hours(1)))
.sideOutputLateData(outputTag)
.aggregate(new MyAggregateFunction())
DataStream<Event> lateStream = winAggStream.getSideOutput(outputTag);

6.3.8

1)窗口创建:窗口不会预先创建好, 而是由数据驱动创建。当第一个应该属于这个窗口的数据元素到达时,就会创建对应的窗口。

2)触发计算:

一个滚动事件时间窗口,应该在 水位线到达窗口结束时间的时候触发计算,属于“定点发车”;

一个计数窗口,会在窗口中 元素数量达到定义大小时触发计算,属于“人满就发车”

当我们设 置了允许延迟,那么如果水位线超过了窗口结束时间、但还没有到达设定的最大延迟时间,这 期间内到达的迟到数据也会触发窗口计算。这类似于没有准时赶上班车的人又追上了车,这时 车要再次停靠、开门,将新的数据整合统计进来。


3) 窗口销毁

一般情况下,当时间达到了结束点,就会直接触发计算输出结果、进而清除状态销毁窗口。 这时窗口的销毁可以认为和触发计算是同一时刻。

在特殊的场景下,窗口的销毁和触发计算会有所不同。事件时间语义下,如果设置了允许 延迟,那么在水位线到达窗口结束时间时,仍然不会销毁窗口;窗口真正被完全删除的时间点, 是窗口的结束时间加上用户指定的允许延迟时间。

6.4 迟到数据的处理 (3板斧)


6.4.1 设置水位线延迟时间

6.4.2 允许窗口处理迟到数据

6.4.3 将迟到数据放入窗口侧输出流

7 处理函数

简述:

处理函数:它是所有转换算子的一个概括性的表达,可以自 定义处理逻辑。

在处理函数中,我们直面的就是数据流中最基本的元素:数据事件(event)、状态(state) 以及时间(time)。这就相当于对流有了完全的控制权。

7.1 基本处理函数(ProcessFunction)

富函数类:比如 RichMapFunction, 它提供了获取运行时上下文的方法 getRuntimeContext(),可以拿到状态,还有并行度、任务名 称之类的运行时信息。

处理函数:实现类中继承了富函数,同时可以掌控水位线和状态。

1) 处理函数的分类.

对于不同的sream.process() 4*2 一共8个如下:

(1)ProcessFunction 最基本的处理函数,基于 DataStream 直接调用.process()时作为参数传入。

(2)KeyedProcessFunction 对流按键分区后的处理函数,基于 KeyedStream 调用.process()时作为参数传入。要想使用 定时器,比如基于 KeyedStream。

(3)ProcessWindowFunction 开窗之后的处理函数,也是全窗口函数的代表。基于 WindowedStream 调用.process()时作 为参数传入。

(4)ProcessAllWindowFunction 同样是开窗之后的处理函数,基于 AllWindowedStream 调用.process()时作为参数传入。

(5)CoProcessFunction 188 合并(connect)两条流之后的处理函数,基于 ConnectedStreams 调用.process()时作为参 数传入。关于流的连接合并操作,我们会在后续章节详细介绍。

(6)ProcessJoinFunction 间隔连接(interval join)两条流之后的处理函数,基于 IntervalJoined 调用.process()时作为 参数传入。

(7)BroadcastProcessFunction 广播连接流处理函数,基于 BroadcastConnectedStream 调用.process()时作为参数传入。这 里的“广播连接流”BroadcastConnectedStream,是一个未 keyBy 的普通 DataStream 与一个广 播流(BroadcastStream)做连接(conncet)之后的产物。关于广播流的相关操作,我们会在后 续章节详细介绍。

(8)KeyedBroadcastProcessFunction 按键分区的广播连接流处理函数,同样是基于 BroadcastConnectedStream 调用.process()时 作为参数传入。与 BroadcastProcessFunction 不同的是,这时的广播连接流,是一个 KeyedStream 与广播流(BroadcastStream)做连接之后的产物。

7.2 按键分区处理函数(KeyedProcessFunction)

7.3 窗口处理函数

7.4 应用案例——Top N

7.5 侧输出流(Side Output)

侧输出流可以认为是“主流”上分叉出的“支流”,所以可以由一条流产生出多条流,而且这 些流中的数据类型还可以不一样。利用这个功能可以很容易地实现“分流”操作。

应用:具体应用时,只要在处理函数的.processElement()或者.onTimer()方法中,调用上下文 的.output()方法就可以了。

8 多流转换

简述:

简单划分的话,多流转换可以分为“分流”和“合流”两大类。目前分流的操作一般是通 过侧输出流(side output)来实现,而合流的算子比较丰富,根据不同的需求可以调用 union、 connect、join 以及 coGroup 等接口进行连接合并操作。

合流: union、无条件连接两个流connnect、有条件连接两个流 join

8.1 分流:

  • 复用变量,调用filter分流
  • 使用侧输出流

8.2 合流

  • 连接流connectedStreams
    union: 需要多个流的类型相同
    connect:允许连接流中每个流数据类型不同,一国两制,同床异梦,可以联合起来的两个流分别处理

datastram.connect(datastream)

CoProcessFunction: 对于连接流 ConnectedStreams 的处理操作需要用到协同处理函数CoProcessFunction

  • 广播连接流(BroadcastConnectedStream) ;普通流和广播流进行连接合并后形成广播连接流

普通流变成广播流


MapStateDescriptor<String, Rule> ruleStateDescriptor = new 
MapStateDescriptor<>(...);

BroadcastStream<Rule> ruleBroadcastStream = ruleStream
 .broadcast(ruleStateDescriptor);

普通流连接广播流并且协同处理

DataStream<String> output = stream
 .connect(ruleBroadcastStream)
 .process( new BroadcastProcessFunction<>() {...} )

8.3 合流

  • 有条件的连接两个流 ( Join)
    Flink 的 DataStrema API 提供了两种内置的 join 算子,以及 coGroup 算子

Flink 中两条流 的 connect 操作,就可以通过 keyBy 指定键进行分组后合并,实现了类似于 SQL 中的 join 操 作;

  • 在flink中的流join大体分为两种,一种是基于时间窗口的join(Time Windowed Join),比如join、coGroup等。另一种是基于状态缓存的join(Temporal Table Join),比如intervalJoin。

8.3.1 窗口联结(Window Join)

stream1.join(stream2)
 .where(<KeySelector>)
 .equalTo(<KeySelector>)
 .window(<WindowAssigner>)
 .apply(<JoinFunction>)  #传入联结窗口函数,只能用apply

8.3.2 间隔联结(Interval Join)

解决窗口数据连接横跨多个窗口

间隔联结的思路就是针对一条流的每个数据,开辟出其时间戳前后的一段时间间隔, 看这期间是否有来自另一条流的数据匹配。(应用:实时对账)


stream1
 .keyBy(<KeySelector>)
 .intervalJoin(stream2.keyBy(<KeySelector>))
 .between(Time.milliseconds(-2), Time.milliseconds(1))
 .process (new ProcessJoinFunction<Integer, Integer, String(){
 @Override
 public void processElement(Integer left, Integer right, Context ctx, 
Collector<String> out) {
 out.collect(left + "," + right);
 }
 });

8.3.3 窗口同组联结(Window CoGroup)

它的用法跟 window join 非常类似,也是将两条流合并之后开窗处理匹配的元素,调用时 只需要将.join()换为.coGroup()就可以了。

区别: coGroup 操作比窗口的 join 更加通用,不仅可以实现类似 SQL 中的“内 连接”(inner join),也可以实现左外连接(left outer join)、右外连接(right outer join)和全外 连接(full outer join)

9 状态编程

状态分类、状态的使用、持久化及状态后 端的配置。

9.1.1 有状态算子

算子可以分为有状态算子,无状态算子,

有状态算子:map、flatmap、filter

无状态算子:聚合算子、窗口算子


9.1.2 状态的管理

  • flink中有两种状态 托管状态(Managed State)和原始状态(Raw State)

托管状态:托管状态就是 由 Flink 统一管理的,状态的存储访问、故障恢复和重组等一系列问题都由 Flink 实现,我们 只要调接口就可以; flink 也提供了值状态(ValueState)、 列表状态(ListState)、映射状态(MapState)、聚合状态(AggregateState)等多种结构

原始状态: 而原始状态则是自定义的,相当于就是开辟了一块内存,需要我们自己管 理,实现状态的序列化和故障恢复。

  1. 算子状态(Operator State)和按键分区状态(Keyed State)

在底层,Keyed State 类似于一个分布式的映射(map)数据结构,所有的状态会根 据 key 保存成键值对(key-value)的形式。

9.1.3 状态的分类、状态描述器(注册)、状态+定时器

  1. 值状态(ValueState)
  2. 列表状态(ListState)
  3. 映射状态(MapState)
  4. 归约状态(ReducingState)
  5. 聚合状态(AggregatingState)

9.2.2 支持的结构类型

9.2.4 状态生存时间(TTL)

设置状态的ttl

StateTtlConfig ttlConfig = StateTtlConfig
 .newBuilder(Time.seconds(10))
 .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
 .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
 .build();
ValueStateDescriptor<String> stateDescriptor = new ValueStateDescriptor<>("my
state", String.class);
260
stateDescriptor.enableTimeToLive(ttlConfig);  ##设置状态(包括ttl)

9.3 算子状态(Operator State)

9.3.2 状态类型

算子状态也支持不同的结构类型,主要有三种:ListState、UnionListState 和 BroadcastState。

  1. 列表状态(ListState)

与 Keyed State 中的列表状态的区别是:在算子状态的上下文中,不会按键(key)分别处 理状态,所以每一个并行子任务上只会保留一个“列表”(list),也就是当前并行子任务上所 有状态项的集合。

  1. 联合列表状态(UnionListState)

9.3 算子状态(Operator State)

  1. 列表状态(ListState)
  2. 联合列表状态(UnionListState)
    联合列表状态也会将状态表示为一个列表。它与常规列表状态的区别在于,算子并行度进行缩放调整时对于状态的分配方式不同。
    UnionListState的重点就在于“联合”(union)。在并行度调整时,常规列表状态是轮询分配状态项,而联合列表状态的算子则会直接广播状态的完整列表
  3. 广播状态(BroadcastState)

9.4 状态持久化和状态后端

9.4.1 检查点(Checkpoint)

有状态流应用中的检查点(checkpoint),其实就是所有任务的状态在某个时间点的一个快照(一份拷贝)。

StreamExecutionEnvironment env = StreamExecutionEnvironment.getEnvironment();
env.enableCheckpointing(1000);

9.4.2 状态后端(State Backends)

状态后端主要负责两件事:一是本地的状态管理,二是将检查点(checkpoint)写入远程的持久化存储。

\1. 状态后端的分类

HashMap和RocksDB两种状态后端最大的区别,就在于本地状态存放在哪里:前者是内存,后者是RocksDB。

对于检查点的保存,一般是放在持久化的分布式文件系统(file system)中,也可以通过配置“检查点存储”(CheckpointStorage)来另外指定。

10 容错机制

10.1 检查点(Checkpoint)

10.1.1 检查点的保存

10.1.4 检查点配置

  1. 启用检查点
StreamExecutionEnvironment env = 
StreamExecutionEnvironment.getExecutionEnvironment();
// 每隔1秒启动一次检查点保存
env.enableCheckpointing(1000);
  1. 检查点存储(Checkpoint Storage)
    默认情况下,检查点存储在JobManager的堆(heap)内存中。而对于大状态的持久化保存,Flink也提供了在其他存储位置进行保存的接口,这就是CheckpointStorage
// 配置存储检查点到JobManager堆内存
env.getCheckpointConfig().setCheckpointStorage(new JobManagerCheckpointStorage());
// 配置存储检查点到文件系统
env.getCheckpointConfig().setCheckpointStorage(new FileSystemCheckpointStorage("hdfs://namenode:40010/flink/checkpoints"));

\3. 其它高级配置

高级配置

说明

l 检查点模式(CheckpointingMode)

精确一次”(exactly-once)和“至少一次”(at-least-once)

l 超时时间(checkpointTimeout)

用于指定检查点保存的超时时间,超时没完成就会被丢弃掉。

l 最小间隔时间(minPauseBetweenCheckpoints)

用于指定在上一个检查点完成之后,检查点协调器(checkpoint coordinator)最快等多久可以出发保存下一个检查点的指令。

l 最大并发检查点数量(maxConcurrentCheckpoints)

l 开启外部持久化存储(enableExternalizedCheckpoints)

检查点的清理工作

l 检查点异常时是否让整个任务失败(failOnCheckpointingErrors)

l 不对齐检查点(enableUnalignedCheckpoints)

StreamExecutionEnvironment env = 
StreamExecutionEnvironment.getExecutionEnvironment();

// 启用检查点,间隔时间1秒
env.enableCheckpointing(1000);
CheckpointConfig checkpointConfig = env.getCheckpointConfig();
// 设置精确一次模式
checkpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 最小间隔时间500毫秒
checkpointConfig.setMinPauseBetweenCheckpoints(500);
// 超时时间1分钟
checkpointConfig.setCheckpointTimeout(60000);
// 同时只能有一个检查点
checkpointConfig.setMaxConcurrentCheckpoints(1);
// 开启检查点的外部持久化保存,作业取消后依然保留
checkpointConfig.enableExternalizedCheckpoints(
    ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// 启用不对齐的检查点保存方式
checkpointConfig.enableUnalignedCheckpoints();
// 设置检查点存储,可以直接传入一个String,指定文件系统的路径
checkpointConfig.setCheckpointStorage("hdfs://my/checkpoint/dir")

10.1.5 保存点(Savepoint)

检查点作用:检查点主要用来做故障恢复,是容错机制的核心;保存点则更加灵活,可以用来做有计划的手动备份和恢复。

保存点与检查点最大的区别,就是触发的时机。检查点是由Flink自动管理的,定期创建,发生故障之后自动读取进行恢复,这是一个“自动存盘”的功能;而保存点不会自动创建,必须由用户明确地手动触发保存操作,所以就是“手动存盘”。因此两者尽管原理一致,

flinksql

1 简述

1 flink sql api

TableEnvironment (表环境、功能如下4个,通过表环境配置能设置流和批两种模式)

EnvironmentSettings (表环境配置,有流和批两种模式)

Table (表)

StreamExecutionEnvironment (流式执行环境)

StreamTableEnvironment (流式表环境、功能有:注册表、执行sql、UDF、流和表转换)

TableSink

表转换为流、流转换为表

通过流环境,将表转换为数据流、输出

StreamTableEnvironment .toDataStream(table)

通过流环境,将流转换为表

StreamTableEnvironment .fromDataStream(datastream)

2 基本API

2.1 程序架构

核心是TableEnvironment 的方法


主要功能是、创建表、转换表、查询后输出、分别对应

// 创建表环境
TableEnvironment tableEnv = ...;
 
// 创建输入表,连接外部系统读取数据  //* 相当于建立连接,读
tableEnv.executeSql("CREATE TEMPORARY TABLE inputTable ... WITH ( 'connector' = ... )");

// 注册一个表,连接到外部系统,用于输出 //* 相当于建立连接,写
tableEnv.executeSql("CREATE TEMPORARY TABLE outputTable ... WITH ( 'connector' = ... )");

// 执行SQL对表进行查询转换,得到一个新的表
Table table1 = tableEnv.sqlQuery("SELECT ... FROM inputTable... ");

// 使用Table API对表进行查询转换,得到一个新的表
Table table2 = tableEnv.from("inputTable").select(...);

// 将得到的结果写入输出表
TableResult tableResult = table1.executeInsert("outputTable");

2.2 创建表环境

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

2.3 创建表

具体创建表的方式,有通过连接器(connector)和虚拟表(virtual tables)两种。

  1. 连接器表(Connector Tables)
    就是通过连接器(connector)连接到一个外部系统,然后定义出对应的表结构。
我们可以调用表环境的executeSql()方法,可以传入一个DDL作为参数执行SQL操作。这里我们传入一个CREATE语句进行表的创建,并通过WITH关键字指定连接到外部系统的连接器:
tableEnv.executeSql("CREATE [TEMPORARY] TABLE MyTable ... WITH ( 'connector' = ... )");
  1. 虚拟表(Virtual Tables)
Table newTable = tableEnv.sqlQuery("SELECT ... FROM MyTable... ");

虚拟表,若需要在sql中使用,需要创建虚拟视图

tableEnv.createTemporaryView("NewTable", newTable);    //* 表名, java对象

2.4 表的查询

Flink为我们提供了两种查询方式:SQL,和Table API。

  1. 执行SQL进行查询 TableEnvironment (sqlQuery 、executeSql )
    tableEnv的方法
  2. 调用Table API进行查询 Table

Table

2.5 输出表 (Table.executeInsert)

2.6 表和流的转换

  1. 将表(Table)转换成流(DataStream)
  • 调用表环境的方法toDataStream()
  • 用的是表环境的toChangelogStream() 【例如分组聚合统计,对于这样有更新操作的表,使用这个方法】
我们不应该直接把它转换成DataStream打印输出,而是记录一下它的“更新日志”(change log)。这样一来,对于表的所有更新操作,就变成了一条更新日志的流,我们就可以转换成流打印输出了
  1. 将流(DataStream)转换成表(Table)
  • 调用表环境的fromDataStream() 【如果流中数据有泛型,则可以取pojo类的全部属性、也可以取部分属性】
由于流中的数据本身就是定义好的POJO类型Event,所以我们将流转换成表之后,每一行数据就对应着一个Event,而表中的列名就对应着Event中的属性。
  • 调用表环境 createTemporaryView()方法 【若需要在sql中引用这个表的话,建议用这个】
createTemporaryView()方法创建虚拟表,传入的两个参数,第一个依然是注册的表名,而第二个可以直接就是DataStream。之后仍旧可以传入多个参数,用来指定表中的字段


tableEnv.createTemporaryView("EventTable", eventStream, $("timestamp").as("ts"),$("url"));
  1. 支持的数据类型

整体来看,DataStream中支持的数据类型,Table中也是都支持的,只不过在进行转换时需要注意一些细节。

(1)原子类型

原子类型的DataStream,转换之后就成了只有一列的Table,列字段(field)的数据类型可以由原子类型推断出。另外,还可以在fromDataStream()方法里增加参数,用来重新命名列字段。

(2)Tuple类型

当原子类型不做重命名时,默认的字段名就是“f0”,容易想到,这其实就是将原子类型看作了一元组Tuple1的处理结果。

所有字段都可以被重新排序,也可以提取其中的一部分字段。字段还可以通过调用表达式的as()方法来进行重命名。

(3)POJO 类型

将POJO类型的DataStream转换成Table,如果不指定字段名称,就会直接使用原始 POJO 类型中的字段名称。POJO中的字段同样可以被重新排序、提却和重命名。

(4)Row类型

Row类型也是一种复合类型,它的长度固定,而且无法直接推断出每个字段的类型,所以在使用时必须指明具体的类型信息

3 流处理中的表

3.1 动态表和持续查询

1. 动态表(Dynamic Tables)

2. 持续查询(Continuous Query)

3.2 将流转换成动态表

3.3 用SQL持续查询

  1. 更新(Update)查询 【tableEnv.sqlQuery】
  • 查询过程用到了分组聚合,结果表中就会产生更新操作; 更新查询得到的结果表如果想要转换成DataStream,必须调用toChangelogStream()方法。
  1. 追加(Append)查询

3.4 将动态表转换为流

动态表也可以通过插入(Insert)、更新(Update)和删除(Delete)操作,进行持续的更改。

  • l 仅追加(Append-only)流
  • l 撤回(Retract)流 【toChangelogStream()得到的其实就是撤回流】
  • l 更新插入(Upsert)流

4 时间属性和窗口

所以所谓的时间属性(time attributes),其实就是每个表模式结构(schema)的一部分。它可以在创建表的DDL里直接定义为一个字段,也可以在DataStream转换成表时定义。

一旦定义了时间属性,它就可以作为一个普通字段引用,并且可以在基于时间的操作中使用。

4.1 事件时间

事件时间属性可以在创建表DDL中定义,也可以在数据流和表的转换中定义。

1. 在创建表的DDL中定义

CREATE TABLE EventTable(
  user STRING,
  url STRING,
  ts TIMESTAMP(3),
  WATERMARK FOR ts AS ts - INTERVAL '5' SECOND
) WITH (
  ...
);
  1. 在数据流转换为表时定义
    事件时间属性也可以在将DataStream 转换为表的时候来定义。我们调用fromDataStream()方法创建表时,可以追加参数来定义表中的字段结构;这时可以给某个字段加上.rowtime() 后缀,就表示将当前字段指定为事件时间属性

需要注意的是,这种方式只负责指定时间属性,而时间戳的提取和水位线的生成应该之前就在DataStream上定义好了。

4.2 处理时间

4.3 窗口(Window)

  1. 分组窗口(Group Window,老版本-弃用不推荐)
  2. 窗口表值函数(Windowing TVFs,新版本)
  • l 滚动窗口(Tumbling Windows)
  • l 滑动窗口(Hop Windows,跳跃窗口)
  • l 累积窗口(Cumulate Windows)
  • l 会话窗口(Session Windows,目前尚未完全支持)

5 聚合查询

5.1 分组聚合

5.2 窗口聚合

1.13版本开始使用了“窗口表值函数”(Windowing TVF),窗口本身返回的是就是一个表 ,所以窗口会出现在FROM后面,GROUP BY后面的则是窗口新增的字段window_start和window_end。

例如:
Table result = tableEnv.sqlQuery(
                        "SELECT " +
                            "user, " +
                            "window_end AS endT, " +
                            "COUNT(url) AS cnt " +
                        "FROM TABLE( " +
                                  "TUMBLE( TABLE EventTable, " +
                                  "DESCRIPTOR(ts), " +
                                  "INTERVAL '1' HOUR)) " +
                        "GROUP BY user, window_start, window_end "
                );

5.3 开窗(Over)聚合

例子

SELECT user, ts,
  COUNT(url) OVER w AS cnt,
  MAX(CHAR_LENGTH(url)) OVER w AS max_url
FROM EventTable
WINDOW w AS (
  PARTITION BY user
  ORDER BY ts
  ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)

6 联结查询

Flink SQL中的联结查询大体上也可以分为两类:SQL原生的联结查询方式,和流处理中特有的联结查询。

6.1 常规联结查询

  1. 等值内联结(INNER Equi-JOIN)
例如:
SELECT *
FROM Order
INNER JOIN Product
ON Order.product_id = Product.id
  1. 等值外联结(OUTER Equi-JOIN)
#左
FROM Order
LEFT JOIN Product
ON Order.product_id = Product.id
#右
SELECT *
FROM Order
RIGHT JOIN Product
ON Order.product_id = Product.id
#全
SELECT *
FROM Order
FULL OUTER JOIN Product
ON Order.product_id = Product.id

6.2 间隔联结查询

  • l 两表的联结
  • 联结条件
  • l 时间间隔限制

例子

SELECT *
FROM Order o, Shipment s
WHERE o.id = s.order_id
AND o.order_time BETWEEN s.ship_time - INTERVAL '4' HOUR AND s.ship_time

7 函数

Flink SQL中的系统函数又主要可以分为两大类:标量函数(Scalar Functions)和聚合函数(Aggregate Functions).

标量函数指的就是只对输入数据做转换操作、返回一个值的函数。

聚合函数是以表中多个行作为输入,提取字段进行聚合操作的函数,会将唯一的聚合值作为结果返回。

7.1 系统函数

7.2 自定义函数(UDF)

Flink的Table API和SQL提供了多种自定义函数的接口,以抽象类的形式定义。当前UDF主要有以下几类:

  • l 标量函数(Scalar Functions):将输入的标量值转换成一个新的标量值;
  • l 表函数(Table Functions):将标量值转换成一个或多个新的行数据,也就是扩展成一个表;
  • l 聚合函数(Aggregate Functions):将多行数据里的标量值转换成一个新的标量值;
  • l 表聚合函数(Table Aggregate Functions):将多行数据里的标量值转换成一个或多个新的行数据。

8 连接到外部系统

在Table API和SQL编写的Flink程序中,可以在创建表的时候用WITH子句指定连接器(connector),这样就可以连接到外部系统进行数据交互了。

flink cep

重要的类

PatternStream

Pattern (模式的api)

GroupPattern

SimpleCondition

CEP.pattern(stream, pattern)

1 简述

总结用途:

1)匹配某个事件

2)匹配多个事件

3) 对单个或多个事件设置时间

4) 挑出匹配部分事件、超时事件【不全匹配多个事件】

不过在实际应用中,还有一类需求是要检测以特定顺序先后发生的一组事件,进行统计或做报警提示,这就比较麻烦了

把事件流中的一个个简单事件,通过一定的规则匹配组合起来,这就是“复杂事件”;然后基于这些满足规则的一组组复杂事件进行转换处理,得到想要的结果进行输出。

总结起来,复杂事件处理(CEP)的流程可以分成三个步骤:
(1)定义一个匹配规则
(2)将匹配规则应用到事件流上,检测满足规则的复杂事件
(3)对检测到的复杂事件进行处理,得到结果进行输出

12.1.2 模式(Pattern)

CEP 的第一步所定义的匹配规则,我们可以把它叫作“模式”(Pattern)。模式的定义主要
就是两部分内容

  • 每个简单事件的特征
  • 简单事件之间的组合关系 (近邻和严格近邻)

12.3 模式 API(Pattern API )

12.3.1 个体模式

所以我们就把每个简单事件的匹配规则,叫作“个体模式”(Individual Pattern)。

  • 每个个体模式都以一个“连接词”开始定义的,比如 begin、next 等等
  • 个体模式需要一个“过滤条件”,用来指定具体的匹配规则
  • “量词”:给个体模式增加一个“量词” (quantifier),就能够让它进行循环匹配,接收多个事件。
  1. 量词(Quantifiers )

量词”,用来指定循环的次数,也就是循环模式

在 Flink CEP 中,可以使用不同的方法指定循环模式,主要有【这些模式能够相互组合使用】

  • oneOrMore()
  • .times(times)
  • times(fromTimes,toTimes)
  • .greedy()
  • .optional()

greedy说明

只能用在循环模式后,使当前循环模式变得“贪心”(greedy),也就是总是尽可能多地去
匹配。例如 a.times(2, 4).greedy(),如果出现了连续 4 个 a,那么会直接把 aaaa 检测出来进行处
理,其他任意 2 个 a 是不算匹配事件的
  1. 条件(Conditions)

主要可以分为限定子类型、简单条件、迭代条件、复合条件、终止条件几种类型

  • 限定子类型 (subtype())
  • 简单条件(Simple Conditions)
本质上其实就是一个 filter 操作。


  • 迭代条件(Iterative Conditions)
    IterativeCondition
简单条件只能基于当前事件做判断,能够处理的逻辑比较有限。
在实际应用中,我们可能需要将当前事件跟之前的事件做对比,才能判断出要不要接受当前事件。
这种需要依靠之前事件来做判断的条件,就叫作“迭代条件”(Iterative Condition)。
  • 组合条件(Combining Conditions)

如果一个个体模式有多个限定条件,又该怎么定义呢?


  • 终止条件(Stop Conditions)

12.3.2 组合模式

begin、next、followedBy

有了定义好的个体模式,就可以尝试按一定的顺序把它们连接起来,定义一个完整的复杂
事件匹配规则了。这种将多个个体模式组合起来的完整模式,就叫作“组合模式

  1. 初始模式(Initial Pattern) 【begin】
Pattern<Event, ?> start = Pattern.<Event>begin("start");
  1. 近邻条件(Contiguity Conditions)

Flink CEP 中提供了三种近邻关系:

  • 严格近邻(Strict Contiguity) 【.next()】
  • 宽松近邻(Relaxed Contiguity) 【.followedBy()】
  • 非确定性宽松近邻(Non-Deterministic Relaxed Contiguity) 【followedByAny()】
    会重复使用之前已经匹配过的事件。
  1. 其他限制条件
  • .notNext()
  • .notFollowedBy()
  • within() 【为cep 模式指定一个时间限制】
  1. 循环模式中的近邻条件
  • consecutive()
    为循环模式中的匹配事件增加严格的近邻条件,保证所有匹配事件是严格连续的。也就是 说,一旦中间出现了不匹配的事件,当前循环检测就会终止。
  • .allowCombinations() 非确定性宽松近邻条件

12.3.3 模式组

模式中嵌套模式

12.3.4 匹配后跳过策略

greddy 贪心尽可能匹配更多的模式,如果我们想要精 确控制事件的匹配应该跳过哪些情况,那就需要制定另外的策略了

4 模式的检测处理

12.4.1 将模式应用到流上

12.4.2 处理匹配事件

\1. 匹配事件的选择提取(select)

\2. 匹配事件的通用处理(process)

12.4.3 处理超时事件

\1. 使用 PatternProcessFunction 的侧输出流

\2. 使用 PatternTimeoutFunction

12.4.4 处理迟到数据

action

flink常用工具类

flink 常用类

其他补充

  • Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。
  • Jackson之ObjectMapper对象的使用
  • kafka 自定义序列化器和反序列化, 发送相同数据到一个或多个topic
  • checkpoint 工具类

工具类

parameterTool类

ParameterTool是Flink提供的读取程序启动参数、配置文件、环境变量以及Flink自身配置参数等配置的的一个工具类。在开发时也经常使用到它。

常用类

  • Instant
  • TimeZone
  • Properties 类