开发者学堂课程【开源 Flink 极客训练营:Stream Processing with Apache Flink】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/760/detail/13338
Stream Processing with Apache Flink
二、DataStream API 概览
1、Flink API 逻辑层次
在旧版本的 Flink 里开发的 API 层次是遵循四层的关系,最上层可以用一种相对而言比较高级的 API 或者是声名程度更高的 Table API 、SQL 来编写逻辑,所有的 SQL、Table、 API 编写的内容都会被内部的翻译,还有优化成一个用 DataStream API 编写的程序,再进一步 Streaming API 的程序会被表示成为一系列transformations,最终 transformations 会被翻译成 JobGraph 可以理解成 DAG 形式,在较新版本 Flink 中发生了一些改变,主要的改变是体现在 Table 、API、SQL,不再会被翻译成DataStream API 的程序,而是直接到达底层 transformations 的形式,DataStream API 和 Table、API、SQL从下层和上层的关系变为平级的关系,流程上的简化也相应地带来一些好处,如果在阅读源码的时候可以关注一下。重点是 Streaming API 。
2、DataStream API 示例
public static void datastream() throws Exception {
//1、获取运行环境
StreamExecutionEnvironment e=StreamExecutionEnvironment. getExecutionEnvironment());
//2、设置source读取数据
Datastream source=e.addsource(
newFromElementsFunction<>(Types.INT.createSerializer(e.getConfig()),data),Types.INT);
//3、对数据进行一系列转换
DataStream ds=source.map(v->v*2).keyBy(value-> 1).sum(0);
//4、将数据写入Sink
ds.addsink(new PrintsinkFunction<>());
//5、提交执行
e.execute();
}
数字乘以二累加的工作,如果把它放到 flash 里面,它的基本的代码就是这样子,可以看出来相对而言要稍微的复杂,比起单机的示例而言要稍微的复杂一点,一步一步的进行讲解,首先第一步要用 Flink 在上面去做开发,一定要获取一个相应的运行环境,比如 Stream Environment,获取环境之后可以调用环境的 Source 方法来为整个的逻辑添加最初始数据源的输入。设置完数据源之后可以拿到数据源的引用,也是 Source 对象可以调用一系列的转化方法来对 Source 中的数据进行一系列的转化,转化是把所有的数字都乘以二,乘以二之后为了求和需要进行一个分组,分组相当于后面返回的一个常数,表示的含义是把所有的数据都分到一组里,最后再对组里所有的数据,按照第一个字段,因为数据里就一个字段是一个类型的一组数进行累加,最终得到结果。得到结果之后不能简单的像单机程序拿到最后的结果把它输出,而是需要在整个的逻辑里面加一个相当于输出一个 Sink 然后把所有的数据写到目标当中,在这些工作进行完之后一定要显示的去调用 environment 里面 SQ 的方法,然后把所有上边编写的逻辑统一提交到远程或者本地的集群上执行。用Flink 的 DataStream API 编写程序和单机程序最大的不同在于前几步的过程都不会触发2、3、4,都不会实际的去触发数据的计算,而是只有在最后一步才可以提交,前面其实是在绘制 DAG 图,前面所有的步骤可以想象成为不停绘图的过程,整个逻辑的 DAG 图绘制完成之后,可以调用 SQ 的方法,整个图作为整体提交上执行。可以把前面介绍的编程风格的 API 还有 DAG 图联系在一起。
3、Flink 作业产生过程
具体的产生过程比较复杂,要经过一步一步的转换还有优化等等措施,Flink 作业产生过程的图现在不用看懂里边的每一步代表了什么,只是对于感兴趣的同学可以把图当成一个脉络梳理,对阅读源码有帮助,比如想看任务是怎么产生的可以对照图里每一块,去源码中找对应的类来观察整个作业的生成过程。
4、DataStream 转换操作
DataStream API 里提供的一些转换操作,像在示例代码中所看到每一个 DataStream 的对象,在调用相应方法的时候相当于都会产生一个新的转化,新的转换对应的某一个新的算子,然后把算子添加到已有的逻辑 DAG 图中,相当于添加一条边来指向现有最后的一个节点,采取不停的去扩展图的方式,所有的 API 调动它的时候,都会产生一个新的对象,可以在新的对象上继续调用它的转换方法链式的方式,一步一步的去把图画出来,涉及到高阶函数的思想,每调用一个 DataStream 上的转换的时候都需要给它传递的一个参数,它里边的 function ,转换决定对于数据进行怎样的操作,实际传递的函数包在算子里面,函数实际上决定了转换操作具体要去怎样完成。除了左边列出来这些 API , Flink、 DataStream 、API里面还有两个非常重要的点是 ProcessFunction 和 CoProcessFunction ,作为最底层的处理逻辑提供给用户使用,所有左侧蓝色涉及的转换,理论上来都可以用底层的 ProcessFunction 还有CoProcess Function 去完成,可以对照着图去梳理中间的转换,主要是理解一下每一个转化实际上都是对数据进行怎样的操作。然后可以去官网上查阅 DataStream、API、Operator 相关文档,看一下每一个转换的具体说明。
5、数据分区(Shuffle)
数据分区在传统的批处理当中对于数据 Shuffle 的操作,传统的批处理里的 Shuffle 操作相当于理牌的过程,在打牌时,拿到牌的时候基本的是把牌给理顺好,按照3、4一直到 J、Q、K 、A 的方式排列好相同的数字要放在一起,最大的好处是在出牌的时候,可以一下去找到你要出的,比如十一下就知道有三个十,可以一下都出出去,也有三个十从三个地方抽出来,这种是少数或者抽牌的方式的效率不如开始把牌分好进行统一的处理,一次性的去拿出来高效是传统的批处理的方式。流处理所有的数据全都是动态到来的,理牌的过程或者对于数据把它按照不同的数字或者花色进行分组分区的过程是动态完成,给出了一个上游,两个 A 的处理实例,下游三个 B 处理的实例,流处理当中的 Shuffle 是数据的分区, A 处理完所发出去的一条数据要把它发送到下游的哪一个处理的实例上,流处理当中 Shuffle 是数据分区的介绍。
6、分区策略
类型 描述
dataStream.keyBy() 按照 Key 值分区
dataStream.global() 全部发往第1个实例
dataStream.broadcast() 广播
dataStream.forward() 上下游并行度一样时一对一发送
dataStream.shuffle() 随机均匀分配
dataStream.rebalance() Round-Robin(轮流分配)
dataStream.rescale() Local Round-Robin(本地轮流分配)
dataStream.partitionCustom() 自定义单播
DataStream 点调用 keyBy 方法之后可以把整个的数据按照一个 K 值进行分区,注意严格上来讲 keyBy 并不算是一种底层的物理分区策略,更多的可以把它想象成转换的操作,因为从 API 的角度上来看会把 DataStream 转化成一个 kid DataStream 的类型,两者所支持的操作也有所不同,可以自己去在代码里面实际的操作一下来感受区别。所有的分区策略里边,可能稍微难理解是 rescale,它涉及到上下游数据的本地性问题,和传统的 balance 是 Round-Robin 轮流分配不同的是,它会尽量的避免数据跨网络的传输。如果所有上述的这些给定的分区策略都不适用,还可以自己调用 partitionCustom 方法去自定义数据。PartitionCustom 只是一种自定义的单播,比如对于每一个数据只能指定一个下游所要发送的实例,没有办法把它复制多份发送到下游的多个实例当中。