Flink入门与实践

592 阅读31分钟

初识Flink

Flink 是 Apache 基金会旗下的一个开源大数据处理框架。目前,Flink 已经成为各大公司大数据实时处理的发力重点,特别是国内以阿里为代表的一众互联网大厂都在全力投入,为Flink 社区贡献了大量源码。如今 Flink 已被很多人认为是大数据实时处理的方向和未来。

那 Flink 到底是什么,又有什么样的优点,能够让大家对它如此青睐?

Flink 的源起和设计理念

Flink 起源于一个叫作 Stratosphere 的项目,它是由地处柏林的大学和欧洲其他一些大学在 2010~2014 年共同进行的研究项目,由柏林理工大学的教授沃克尔·马尔科(Volker Markl) 领衔开发。2014 年 4 月,Stratosphere 的代码被复制并捐赠给了 Apache 软件基金会,Flink 就是在此基础上被重新设计出来的。

在德语中,“flink”一词表示“快速、灵巧”。项目的 logo  是一只彩色的松鼠,当然了,这不仅是因为 Apache 大数据项目对动物的喜好(是否联想到了 Hadoop、Hive?),更是因为松鼠这种小动物完美地体现了“快速、灵巧”的特点。关于 logo 的颜色,还一个有趣的缘由: 柏林当地的松鼠非常漂亮,颜色是迷人的红棕色;而 Apache 软件基金会的logo,刚好也是一根以红棕色为主的渐变色羽毛。于是,Flink 的松鼠Logo 就设计成了红棕色,而且拥有一个漂亮的渐变色尾巴,尾巴的配色与 Apache 软件基金会的 logo 一致。这只松鼠色彩炫目,既呼应了 Apache 的风格,似乎也预示着 Flink 未来将要大放异彩。

图片1.png 从命名上,我们也可以看出 Flink 项目对于自身特点的定位,那就是对于大数据处理,要做到快速和灵活。

⚫ 2014 年 8 月,Flink 第一个版本 0.6 正式发布(至于 0.5 之前的版本,在Stratosphere 名下)。与此同时 Fink 的几位核心开发者创办了Data Artisans 公司, 主要做 Fink 的商业应用,帮助企业部署大规模数据处理解决方案。

⚫ 2014 年 12 月,Flink 项目完成了孵化,一跃成为Apache 软件基金会的顶级项目。

⚫ 2015 年 4 月,Flink 发布了里程碑式的重要版本 0.9.0,很多国内外大公司也正是从这时开始关注、并参与到 Flink 社区建设的。

⚫ 2019 年 1 月,长期对 Flink 投入研发的阿里巴巴,以 9000 万欧元的价格收购了 Data Artisans 公司;之后又将自己的内部版本 Blink 开源,继而与 8 月份发布的 Flink 1.9.0 版本进行了合并。自此之后,Flink 被越来越多的人所熟知,成为当前最火的新一代大数据处理框架。

Flink 从真正起步到火爆,只不过几年时间。在这短短几年内,Flink 从最初的第一个稳定版本 0.9,到目前本书编写期间已经发布到了 1.17.1,这期间不断有新功能新特性加入。从一开始,Flink 就拥有一个非常活跃的社区,而且一直在快速成长。到目前为止,Flink 的代码贡献者(Contributors)已经超过 800 人,并且 Flink 已经发展成为最复杂的开源流处理引擎之一,得到了广泛的应用。

根据Apache  软件基金会发布的 2020  年度报告,Flink 项目的社区参与和贡献依旧非常活跃,在 Apache 旗下的众多项目中保持着多项领先:

⚫ 邮件列表(Mailing List)活跃度,排名第一

⚫ 代码提交(Commits)数,排名第二

⚫ GitHub 访问量,排名第二

Flink 就像一列高速行进的列车,向我们呼啸而来,朝着未来更实时、更稳定的大数据处理奔去。这辆通向未来的车,我们上车可以迟,但一定不要错过。

Flink 的官网主页地址:flink.apache.org/

flink的核心目标,是“数据流上的有状态计算”(Stateful Computations over Data Streams)。

Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams. Flink has been designed to run in all common cluster environments, perform computations at in-memory speed and at any scale.

Flink是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。Flink 被设计在所有常见的集群环境中运行,以内存执行速度和任意规模来执行计算;

突出了 Flink  的两个特点:速度快、可扩展性强

图片2.png

Flink 的应用

 Flink 在企业中的应用

Flink 为全球许多公司和企业的关键业务应用提供了强大的支持。

对于数据处理而言,任何行业、任何公司的需求其实都是一样的:数据规模大、实时性要求高、确保结果准确、方便扩展、故障后可恢复——而这些要求,作为新一代大数据流式处理引擎的 Flink 统统可以满足!这也正是 Flink 在全世界范围得到广泛应用的原因

以下是 Flink 官网列出的知名企业用户

3.png

flink.apache.org/powered-by/ 以阿里为例。阿里巴巴这个庞大的电商公司,为买方和卖方提供了交易平台。它的个性化搜索和实时推荐功能就是通过 Flink 实现的。用户所购买或者浏览的商品,可以被用作推荐的依据,这就是为什么我们经常发现“刚看过什么、网站就推出来了”。当用户数据量非常庞大时,快速地分析响应、实时做出精准的推荐就显得尤为困难。而 Flink 这样真正意义上的大数据流处理引擎,就能做到这些。

 Flink 的应用场景

4.png

流式数据处理的发展和演变

流处理和批处理

数据处理有不同的方式。

对于具体应用来说,有些场景数据是一个一个来的,是一组有序的数据序列,我们把它叫作“数据流”;而有些场景的数据,本身就是一批同时到来,是一个有限的数据集,这就是批量数据(有时也直接叫数据集)。

处理数据流,应该“来一个就处理一个”,这种数据处理模式就叫作流处理;因为这种处理是即时的,所以也叫实时处理。与之对应,处理批量数据自然就应该一批读入、一起计算,这种方式就叫作批处理

在 IT 应用场景中,这一点会体现得更加明显。企业的绝大多数应用程序,都是在不停地接收用户请求、记录用户行为和系统日志,或者持续接收采集到的状态信息。所以数据会在不同的时间持续生成,形成一个有序的数据序列——这就是典型的数据流

很显然,对于流式数据,用流处理是最好、也最合理的方式。但传统的数据处理架构并不是这样。无论是关系型数据库、还是数据仓库,都倾向于先“收集数据”,然后再进行处理,这是因为分布式批处理在架构上更容易实现。

传统事务处理

5.png On-Line Transaction Processing 联机事务处理过程(OLTP)

这就是传统的“事务处理”架构。系统所处理的连续不断的事件,其实就是一个数据流。而对于每一个事件,系统都在收到之后进行相应的处理,这也是符合流处理的原则的。所以可以说,传统的事务处理,就是最基本的流处理架构。

这样的架构对表和数据库的设计要求很高;当数据规模越来越庞大、系统越来越复杂时, 可能需要对表进行重构,而且一次联表查询也会花费大量的时间,甚至不能及时得到返回结果。

联机分析处理(on-Line Analytic Processing)

数据量很大,离线处理,但是不够实时

有状态的流处理

6.png

直接将状态保存在本地内存,当应用收到一个新事件时,它可以从状态中读取数据,也可以更新状态。而当状态是从内存中读写的时候, 这就和访问本地变量没什么区别了,实时性可以得到极大的提升。

数据规模增大时,可以构建分布式集群;可以定期地将应用状态的一致性检查点(checkpoint)存盘,写入远程的持久化存储,遇到故障时再去读取进行恢复,这样就保证了更好的容错性。

缺点:无法保证数据处理的先后顺序

Lambda 架构

对于批处理来说,这并不是一个问题。因为所有数据都已收集完毕,我们可以根据需要选择、排列数据,得到想要的结果;

与批处理器相比,可以说第一代流处理器牺牲了结果的准确性,用来换取更低的延迟。而批处理器恰好反过来,牺牲了实时性,换取了结果的准确。如果可以让二者做个结合,不就可以同时提供快速和准确的结果了吗?正是基于这样的思想,Lambda 架构被设计出来;

7.png

它的“批处理层”(Batch Layer)就是由传统的批处理器和存储组成,而“实时层”(Speed Layer)则由低延迟的流处理器实现。数据到达之后,两层处理双管齐下,一方面由流处理器进行实时处理,另一方面写入批处理存储空间, 等待批处理器批量计算。流处理器快速计算出一个近似结果,并将它们写入“流处理表”中。而批处理器会定期处理存储中的数据,将准确的结果写入批处理表,并从快速表中删除不准确的结果。最终,应用程序会合并快速表和批处理表中的结果,并展示出来。

Lambda 架构现在已经不再是最先进的,但仍在许多地方使用。它的优点非常明显,就是兼具了批处理器和第一代流处理器的特点,同时保证了低延迟和结果的准确性。而它的缺点同样非常明显。首先,Lambda 架构本身就很难建立和维护;而且,它需要我们对一个应用程序, 做出两套语义上等效的逻辑实现,因为批处理和流处理是两套完全独立的系统,它们的 API 也完全不同。为了实现一个应用,付出了双倍的工作量,这对程序员显然不够友好

新一代流处理器

新一代流处理器通过巧妙的设计,完美解决了乱序数据对结果正确性的影响。这一代系统还做到了精确一次(exactly-once)的一致性保障,是第一个具有一致性和准确结果的开源流处理器。

Flink 的特性总结

  1. 高吞吐、低延迟;(每秒处理数百万个事件,毫秒级延迟)
  2. 结果的准确性;(Flink 提供了事件时间(event-time)和处理时间(processing-time) 语义。对于乱序事件流,事件时间语义仍然能提供一致且准确的结果)
  3. 可以与众多常用存储系统链接;(如Apache Kafka、Elasticsearch、JDBC、分布式文件系统)
  4. 高可用,支持动态扩展

流处理的应用场景

事件驱动型(Event-Driven)应用

11.png 事件驱动型应用是一类具有状态的应用,它从一个或多个事件流提取数据,并根据到来的事件触发计算、状态更新或其他外部动作。比较典型的就是以 Kafka 为代表的消息队列几乎都是事件驱动型应用

数据分析(Data Analysis)型应用

12.png 所谓的数据分析,就是从原始数据中提取信息和发掘规律。传统上,数据分析一般是先将数据复制到数据仓库(Data  Warehouse),然后进行批量查询。如果数据有了更新,必须将最新数据添加到要分析的数据集中,然后重新运行查询或应用程序。

与批处理分析相比,流处理分析最大的优势就是低延迟,真正实现了实时。另外,流处理不需要去单独考虑新数据的导入和处理,实时更新本来就是流处理的基本模式。当前企业对流式数据处理的一个热点应用就是实时数仓,很多公司正是基于 Flink 来实现的。

数据管道(Data Pipeline)型应用

13.png 所谓数据管道的作用与 ETL 类似。它们可以转换和扩展数据,也可以在存储系统之间移动数据。不过如果我们用流处理架构来搭建数据管道,通过cdc(debezium)捕获实时数据,这些工作就可以连续运行,而不需要再去周期性触发了;

Flink的分层api

  • 越顶层越抽象,表达含义越简明,使用越方便
  • 越底层越具体,表达能力越丰富,使用越灵活

10.png

Flink部署

部署架构及环境

Flink 是一个分布式的流处理框架,所以实际应用一般都需要搭建集群环境。JobManager 就是 Flink 集群里的“管事人”,对作业进行中央调度管理;而它获取到要执行的作业后,会进一步处理转换,然后分发任务给众多的TaskManager。这里的 TaskManager,就是真正“干活的人”。

8.png

需要准备 3 台Linux 机器。具体要求如下:

  • 安装 Java 8。

  • 配置集群节点服务器间时间同步以及免密登录,关闭防火墙。

服务部署

(1)将flink-1.15.0-bin-scala_2.12.tgz 文件夹分别拷贝到对应的服务器上(JobManager服务器和TaskManager服务器,请参阅部署架构),所有机器上都需要在相同路径访问。

(2)解压每台服务压缩包: tar zxvf flink-1.15.0-bin-scala_2.12.tgz

(3)设置权限 chmod -R 777 flink-1.15.0

(4)修改配置文件(每台机器均要设置):修改flink-1.15.0/conf/flink-conf.yaml文件

参数参数说明例子
jobmanager.rpc.addressJobManager的服务器IP地址172.16.0.189
taskmanager.numberOfTaskSlots采集任务槽,默认200,由项目情况决定,一般无需修改200 

(5)修改{解压文件夹}/conf/workers文件

将TaskManager节点的IP配置到该文件下,例如TaskManager节点IP分别为10.0.0.2和10.0.0.3则配置文件内容如下:

$ vi workers
10.0.0.2
10.0.0.3

免密登录

由于是集群环境,JobManager将通过ssh命令访问TaskManager节点,若不开启免密登录,启动时需每次输入节点的对应访问密码,开启免密方法如下:

(1)JobManager节点执行命令ssh-keygen -t rsa,并回车确认。

(2)复制生成的id_rsa.pub文件导所有节点:

   scp -p ~/.ssh/id_rsa.pub {USER}@{TASKMANAGER_IP}:/{USER}/.ssh/authorized_keys

 例如用户为root用户,集群节点为10.0.0.3,则示例语句如下:

scp -p ~/.ssh/id_rsa.pub root@10.0.0.3:/root/.ssh/authorized_keys

启动服务

(1)登录JobManager,进入目录{解压文件夹}/bin目录,执行start-cluser.sh命令

浏览器中输入:http://{JobManager_IP}:18081,如打开如下界面,则启动成功。

9.png (2)停止服务:登录JobManager,进入目录{解压文件夹}/bin目录,执行stop-cluser.sh命令

参数配置

  • jobmanager.memory.process.size:对 JobManager 进程可使用到的全部内存进行配置, 包括 JVM 元空间和其他开销,默认为 1600M,可以根据集群规模进行适当调整。

  •  taskmanager.memory.process.size:对 TaskManager 进程可使用到的全部内存进行配置,包括 JVM 元空间和其他开销,默认为 1600M,可以根据集群规模进行适当调整。

  •  taskmanager.numberOfTaskSlots:对每个 TaskManager 能够分配的 Slot 数量进行配置, 默认为 1,可根据 TaskManager 所在的机器能够提供给 Flink 的 CPU 数量决定。所谓Slot 就是TaskManager 中具体运行一个任务所分配的计算资源。

  • parallelism.default:Flink 任务执行的默认并行度,优先级低于代码中进行的并行度配置和任务提交时使用参数指定的并行度数量

部署模式

会话模式(Session Mode)

会话模式其实最符合常规思维。我们需要先启动一个集群,保持一个会话,在这个会话中通过客户端提交作业。集群启动时所有资源就都已经确定,所以所有提交的作业会竞争集群中的资源。

这样的好处很明显,我们只需要一个集群,就像一个大箱子,所有的作业提交之后都塞进去;集群的生命周期是超越于作业之上的,铁打的营盘流水的兵,作业结束了就释放资源,集群依然正常运行。当然缺点也是显而易见的:因为资源是共享的,所以资源不够了,提交新的作业就会失败。另外,同一个 TaskManager 上可能运行了很多作业,如果其中一个发生故障导致 TaskManager 宕机,那么所有作业都会受到影响

单作业模式(Per-Job Mode) 会话模式因为资源共享会导致很多问题,所以为了更好地隔离资源,我们可以考虑为每个提交的作业启动一个集群,这就是所谓的单作业(Per-Job)模式;

单作业模式也很好理解,就是严格的一对一,集群只为这个作业而生。同样由客户端运行应用程序,然后启动集群,作业被提交给 JobManager,进而分发给 TaskManager 执行。作业作业完成后,集群就会关闭,所有资源也会释放。这样一来,每个作业都有它自己的 JobManager 管理,占用独享的资源,即使发生故障,它的 TaskManager 宕机也不会影响其他作业。

这些特性使得单作业模式在生产环境运行更加稳定,所以是实际应用的首选模式;

Flink 本身无法直接这样运行,所以单作业模式一般需要借助一些资源管理框架来启动集群,比如 YARN

Flink快速上手

单词频次统计demo

场景:统计一段文字中,每个单词出现的频次

算法:先逐行读入文件数据,然后将每一行文字拆分成单词;接着按照单词分组,统计每组数据的个数,就是对应单词的频次

代码实现:

import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;

public class BoundedStreamWordCount {
    public static void main(String[] args) throws  Exception{
        //1.创建流式执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2.从文件中读取数据
        DataStreamSource<String> lineDataStreamSource = env.readTextFile("input/word.txt");
        //3.将每行数据分词转成二元数组
        SingleOutputStreamOperator<Tuple2<String, Long>> wordAndOneTuple = 
           lineDataStreamSource.flatMap((String line, Collector<Tuple2<String, Long>> out) -> {
                    //将一行文本进行分词
                    String[] words = line.split(" ");
                    // 将每个单词转换成二元组输出
                    for (String word : words) {
                        out.collect(Tuple2.of(word,1L));
                    }
                })
                //当 lambda 表达式使用 java 泛型时 由于泛型擦除的存在,需要显示声明类型信息
                .returns(Types.TUPLE(Types.STRING,Types.LONG));
        //4.按照word进行分组
        KeyedStream<Tuple2<String ,Long>,String> wordAndOneKeyedStream = wordAndOneTuple.keyBy(data -> data.f0);
        //5、分组内进行聚合统计
        SingleOutputStreamOperator<Tuple2<String,Long>> sum = wordAndOneKeyedStream.sum(1);
        //6、打印输出
        sum.print();

        //7、启动执行
        env.execute();
    }
}

输出结果如下:

6> (word,1)
5> (fink,1)
3> (hello,1)
3> (hello,2)
3> (hello,3)
3> (bigdata,1)

打印结果中,“hello”这个单词每出现一次,都会有一个频次统计数据输出。这就是流处理的特点,数据逐个处理,每来一条数据就会处理输出一次。我们通过打印结果,可以清晰地看到单词“hello”数量增长的过程;

疑问:

1、行应该是“hello flink”,怎么这里输出的第一个单词是“world”?

Flink 是一个分布式处理引擎,所以我们的程序应该也是分布式运行的。在开发环境里,会通过多线程来模拟 Flink 集群运行。所以这里结果前的数字, 其实就指示了本地执行的不同线程,对应着 Flink 运行时不同的并行资源。既然是并行执行,不同线程的输出结果,自然也就无法保持输入的顺序了

2、输出的结果二元组,前面都有一个数字,这又是什么呢?

显示的编号为 1~8是由于运行电脑的 CPU 是 8 核,所以默认模拟的并行线程有 8 个。这段代码不同的运行环境,得到的结果会是不同的。关于 Flink 程序并行执行的数量,可以通过设定“并行度”(Parallelism)来进行配置

读取实时数据流

在实际的生产环境中,真正的数据流其实是无界的,有开始却没有结束,这就要求我们需要保持一个监听事件的状态,持续地处理捕获的数据。

为了模拟这种场景,我们就不再通过读取文件来获取数据了,而是监听数据发送端主机的指定端口,统计发送来的文本数据中出现过的单词的个数,

代码实现:

//1.创建流式执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

//2.从文件中读取数据
DataStreamSource<String> lineDataStream = env.socketTextStream("172.17.0.137",7777);
//3.将每行数据分词转成二元数组
SingleOutputStreamOperator<Tuple2<String, Long>> wordAndOneTuple = 
        lineDataStream.flatMap((String line, Collector<Tuple2<String, Long>> out) -> {
            //将一行文本进行分词
            String[] words = line.split(" ");
            // 将每个单词转换成二元组输出
            for (String word : words) {
                out.collect(Tuple2.of(word,1L));
            }
        })
        //当 lambda 表达式使用 java 泛型时 由于泛型擦除的存在,需要显示声明类型信息
        .returns(Types.TUPLE(Types.STRING,Types.LONG));
//安装word进行分组
KeyedStream<Tuple2<String ,Long>,String> wordAndOneKeyedStream = wordAndOneTuple.keyBy(data -> data.f0);
//分组内进行聚合统计
SingleOutputStreamOperator<Tuple2<String,Long>> sum = wordAndOneKeyedStream.sum(1);
//打印输出
sum.print();

//启动执行
env.execute();

1、在 Linux 环境的主机172.17.0.137 上,执行下列命令,发送数据进行测试:nc -lk 7777

2、启动 StreamWordCount 程序,输出的结果与之前读取文件的流处理非常相似

通过WebUi提交作业

已在开发环境的模拟集群上做了运行测试。现在既然已经有了真正的集群环境,那接下如何将任务提交到集群中进行执行,体步骤如下:

1、把代码打成jar包,在137上开启端口监听:nc -lk 7777

2、任务打包完成后,我们打开 Flink 的 WEB UI 页面,在右侧导航栏点击“Submit New Job”,然后点击按钮“+ Add New”,选择要上传运行的 JAR 包

3、点击该JAR 包,出现任务配置页面,进行相应配置。 主要配置程序入口主类的全类名:com.mc.fink.wordCount.StreamWordCount

任务运行的并行度,任务运行所需的配置参数和保存点路径等

4、点击该任务,可以查看任务运行的具体情况,也可以通过点击“Cancel Job”结束任务运行

1.jpg

2.jpg

5、命令行提交作业 (1)除了通过 WEB UI 界面提交任务之外,也可以直接通过命令行来提交任务。这里为方便起见,我们可以先把 jar 包直接上传到目录flink-1.15.0/jar-upload/test (2) 进入到Flink 的安装路径下,在命令行使用 flink run 命令提交作业:

./bin/flink run -m 172.16.0.189:18081 -c com.mc.fink.wordCount.StreamWordCount -p 2 ./jar-upload/test/example-Flink-1.0.jar

Flink 运行时架构

 系统架构

整体构成

Flink 的运行时架构中,最重要的就是两大组件:作业管理器(JobManger)和任务管理器(TaskManager)。对于一个提交执行的作业,JobManager 是真正意义上的“管理者”(Master),负责管理调度。Flink 的作业提交和任务处理时的系统架构如下图所示:

图片1.png

客户端并不是处理系统的一部分,它只负责作业的提交。具体来说,就是调用程序的 main 方法,将代码转换成“数据流图”(Dataflow Graph),并最终生成作业图(JobGraph),一并发送给 JobManager。提交之后,任务的执行其实就跟客户端没有关系了;

作业管理器(JobManager)

对于一个分布式系统来说,也需要面对很多棘手的问题。其中的核心问题有:集群中资源的分配和管理、进程协调调度、持久化和高可用的数据存储,以及故障恢复。

对于这些分布式系统的经典问题,业内已有比较成熟的解决方案和服务。所以 Flink 并不会自己去处理所有的问题,而是利用了现有的集群架构和服务,这样它就可以把精力集中在核心工作——分布式数据流处理上了。Flink 可以配置为独立(Standalone)集群运行,也可以方便地跟一些集群资源管理工具集成使用,比如 YARN、Kubernetes。Flink 也不会自己去提供持久化的分布式存储,而是直接利用了已有的分布式文件系统(比如 HDFS)或者对象存储(比如 S3)。而对于高可用的配置,Flink 是依靠 Apache ZooKeeper 来完成的。

1. JobMaster

JobMaster 是 JobManager 中最核心的组件,负责处理单独的作业(Job)。所以 JobMaster和具体的 Job 是一一对应的,多个 Job 可以同时运行在一个 Flink 集群中, 每个 Job 都有一个自己的 JobMaster。需要注意在早期版本的 Flink 中,没有 JobMaster 的概念;而 JobManager 的概念范围较小,实际指的就是现在所说的 JobMaster。

在作业提交时,JobMaster 会先接收到要执行的应用。这里所说“应用”一般是客户端提交来的,包括:Jar 包,数据流图(dataflow graph),和作业图(JobGraph)。

JobMaster 会把 JobGraph 转换成一个物理层面的数据流图,这个图被叫作“执行图”

( ExecutionGraph ), 它包含了所有可以并发执行的任务。 JobMaster 会向资源管理器

(ResourceManager)发出请求,申请执行任务必要的资源。一旦它获取到了足够的资源,就会将执行图分发到真正运行它们的 TaskManager 上。

而在运行过程中,JobMaster 会负责所有需要中央协调的操作。

2. 资源管理器(ResourceManager)

ResourceManager 主要负责资源的分配和管理,在 Flink  集群中只有一个。所谓“资源”,主要是指TaskManager 的任务槽(task slots)。任务槽就是 Flink 集群中的资源调配单元,包含了机器用来执行计算的一组 CPU 和内存资源。每一个任务(Task)都需要分配到一个 slot 上执行。

新的作业申请资源时,ResourceManager 会将有空闲槽位的 TaskManager 分配给 JobMaster。如果 ResourceManager 没有足够的任务槽,它还可以向资源提供平台发起会话, 请求提供启动 TaskManager 进程的容器。另外, ResourceManager 还负责停掉空闲的TaskManager,释放计算资源。

3. 分发器(Dispatcher)

Dispatcher 主要负责提供一个 REST 接口,用来提交应用,并且负责为每一个新提交的作业启动一个新的 JobMaster 组件。Dispatcher 也会启动一个 Web UI,用来方便地展示和监控作业执行的信息

任务管理器(TaskManager)

TaskManager 是 Flink 中的工作进程,数据流的具体计算就是它来做的,所以也被称为“Worker”。Flink  集群中必须至少有一个 TaskManager;当然由于分布式计算的考虑,通常会有多个 TaskManager 运行,每一个 TaskManager 都包含了一定数量的任务槽(task slots)。Slot是资源调度的最小单位,slot 的数量限制了TaskManager 能够并行处理的任务数量。

启动之后,TaskManager 会向资源管理器注册它的 slots;收到资源管理器的指令后, TaskManager 就会将一个或者多个槽位提供给 JobMaster 调用,JobMaster 就可以分配任务来执行了。

在执行过程中,TaskManager 可以缓冲数据,还可以跟其他运行同一应用的 TaskManager交换数据。

 作业提交流程

Flink 的提交流程,随着部署模式、资源管理平台的不同,会有不同的变化。首先我们从一个高层级的视角,来做一下抽象提炼,看一看作业提交时宏观上各组件是怎样交互协作的

图片2.png

(1) 一般情况下,由客户端(App)通过分发器提供的 REST 接口,将作业提交给

JobManager。

(2) 由分发器启动 JobMaster,并将作业(包含 JobGraph)提交给 JobMaster。

(3) JobMaster 将 JobGraph 解析为可执行的ExecutionGraph,得到所需的资源数量,然后向资源管理器请求资源(slots)。

(4) 资源管理器判断当前是否由足够的可用资源;如果没有,启动新的 TaskManager。

(5) TaskManager 启动之后,向ResourceManager 注册自己的可用任务槽(slots)。

(6) 资源管理器通知 TaskManager 为新的作业提供 slots。

(7) TaskManager 连接到对应的 JobMaster,提供 slots。

(8) JobMaster 将需要执行的任务分发给TaskManager。

TaskManager 执行任务,互相之间可以交换数据

 一些重要概念

数据流图(Dataflow Graph)

Flink 是流式计算框架。它的程序结构,其实就是定义了一连串的处理操作,每一个数据输入之后都会依次调用每一步计算。在 Flink 代码中,我们定义的每一个处理转换操作都叫作“算子”(Operator),所以我们的程序可以看作是一串算子构成的管道,数据则像水流一样有序地流过。比如在之前的 WordCount 代码中,基于执行环境调用的 socketTextStream()方法,就是一个读取文本流的算子;而后面的 flatMap()方法,则是将字符串数据进行分词、转换成二元组的算子。

a1.jpg

所有的 Flink 程序都可以归纳为由三部分构成:Source、Transformation 和 Sink。

⚫ Source 表示“源算子”,负责读取数据源。

⚫ Transformation 表示“转换算子”,利用各种算子进行处理加工。

⚫ Sink 表示“下沉算子”,负责数据的输出。

并行度(Parallelism)

在 Flink 执行过程中,每一个算子(operator)可以包含一个或多个子任务(operator subtask),这些子任务在不同的线程、不同的物理机或不同的容器中完全独立地执行。

一个特定算子的子任务(subtask)的个数被称之为其并行度(parallelism)。这样,包含并行子任务的数据流,就是并行数据流,它需要多个分区(stream partition)来分配并行任务。一般情况下,一个流程序的并行度,可以认为就是其所有算子中最大的并行度。一个程序中, 不同的算子可能具有不同的并行度

任务槽(Task Slots)

Flink 中每一个 worker(也就是 TaskManager)都是一个 JVM 进程,它可启动多个独立的线程,来并行执行多个子任务(subtask)。

TaskManager 的计算资源是有限的,并不是所有任务都可以放在一个 TaskManager上并行执行。并行的任务越多,每个线程的资源就会越少。那一个 TaskManager 到底能并行处理多少个任务呢?为了控制并发量,我们需要在 TaskManager 上对每个任务运行所占用的资源做出明确的划分,这就是所谓的任务槽(task slots)。 每个任务槽(task slot)其实表示了 TaskManager 拥有计算资源的一个固定大小的子集。这些资源就是用来独立执行一个子任务的

任务槽和并行度的关系

我们可以通过集群的配置文件来设定 TaskManager 的 slot 数量:

taskmanager.numberOfTaskSlots: 8

lot 目前仅仅用来隔离内存,不会涉及 CPU 的隔离。在具体应用时,可以将 slot 数量配置为机器的CPU 核心数,尽量避免不同任务之间对 CPU 的竞争

Slot 和并行度确实都跟程序的并行执行有关,但两者是完全不同的概念。简单来说,task slot 是 静 态 的 概 念 , 是 指 TaskManager 具 有 的 并 发 执 行 能 力 , 可 以 通 过 参 数taskmanager.numberOfTaskSlots 进行配置; 而并行度( parallelism ) 是动态概念, 也就是TaskManager 运行程序时实际使用的并发能力,可以通过参数 parallelism.default 进行配置。换句话说,并行度如果小于等于集群中可用 slot 的总数,程序是可以正常执行的,因为 slot 不一定要全部占用,有十分力气可以只用八分;而如果并行度大于可用 slot 总数,导致超出了并行能力上限,程序就只好等待资源管理器分配更多的资源了

Table API 和SQL

10.png

在 Flink 提供的多层级API 中,核心是 DataStream API,这是我们开发流处理应用的基本途径;不过在企业实际应用中,往往会面对大量类似的处理逻辑,所以一般会将底层 API 包装成更加具体的应用级接口。我们最为熟悉的数据统计方式,当然就是写 SQL 了。

Flink 提供了对于“表”处理的支持,这就是更高层级的应用API,在 Flink 中被称为Table API 和 SQL。Table API 顾名思义,就是基于“表”(Table)的一套 API,它是内嵌在 Java、 Scala 等语言中的一种声明式领域特定语言(DSL),也就是专门为处理表而设计的;在此基础上,Flink 还基于Apache Calcite 实现了对 SQL 的支持。这样一来,我们就可以在 Flink 程序中直接写 SQL 来实现处理需求了。

基本API

在 Flink 中,Table API 和 SQL 可以看作联结在一起的一套 API,这套 API 的核心概念就是“表”(Table)。在我们的程序中,输入数据可以定义成一张表;然后对这张表进行查询,就可以得到新的表,这相当于就是流数据的转换操作;最后还可以定义一张用于输出的表,负责将处理结果写入到外部系统。

整体处理流程与 DataStream API 非常相似,也可以分为读取数据源(Source)、转换(Transform)、输出数据(Sink)三部分;只不过这里的输入输出操作不需要额外定义,只需要将用于输入和输出的表定义出来,然后进行转换查询就可以了


// 创建表环境
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");

// 将查询结果输出到 OutputTable 中

tableEnv.executeSql ( "INSERT INTO OutputTable 
SELECT user, url FROM EventTable WHERE user = 'aaa' "

目前 Flink 支持标准 SQL 中的绝大部分用法,并提供了丰富的计算函数。这样我们就可以把已有的技术迁移过来,像在 MySQL中那样直接通过编写 SQL 实现自己的处理需求,从而大大降低了 Flink 上手的难度

 表和流的转换

1. 将表(Table)转换成流(DataStream), 调用 toDataStream()方法

2. 将流(DataStream)转换成表(Table), 调用 fromDataStream()方法

 动态表(Dynamic Tables)

动态表是Flink 在Table API 和SQL 中的核心概念,它为流数据处理提供了表和SQL 支持。我们所熟悉的表一般用来做批处理,面向的是固定的数据集,可以认为是“静态表”;而动态表则完全不同,它里面的数据会随时间变化.

动态表的概念,我们在传统的关系型数据库中已经有所接触。数据库中的表,其实是一系列 INSERT、UPDATE 和 DELETE 语句执行的结果;在关系型数据库中,我们一般把它称为更新日志流(changelog stream)。如果我们保存了表在某一时刻的快照(snapshot),那么接下来只要读取更新日志流,就可以得到表之后的变化过程和最终结果了。在很多高级关系型数据库(比如 Oracle、DB2)中都有“物化视图”(Materialized Views)的概念,可以用来缓存 SQL 查询的结果;它的更新其实就是不停地处理更新日志流的过程;

flink 底层cdc处理是基于 debezium 进行的封装;

sqlserfer cdc 开启:

EXEC sys.sp_cdc_enable_table

@source_schema =N'dbo',

@source_name = N'tableName',

@capture_instance = NULL,

@role_name = NULL,

@supports_net_changes = 0

go

SELECT is_tracked_by_cdc FROM sys.tables WHERE name='tableName';