Flink
- 1 Flink 重要特点
- 1.1 事件驱动型(Event-driven)
- 1.2 流与批的世界观
- 1.3 分层 api
- 1.4支持有状态计算
- 1.5 支持 exactly-once 语义
- 1.6 支持事件时间(EventTime)
- 2 Flink操作环境上手
- 2.1 搭建Flink的工作环境
- 2.2 有界数据流处理(数据离线处理)
- 2.4 无界数据流(实时)处理示例
- 3 集群环境搭建&Yarn模式
- Yarn模式
- Apache Flink 是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。Flink 被设计在所有常见的集群环境中运行,以内存执行速度和任意规模来执行计算。
- 有界数据流: 离线数据
无界数据流:实时数据
既可以处理离线数据;又可以处理实时数据的计算引擎 - Flink也是基于内存的计算方式
- 对于有界数据流的处理:
Flink目前对于离线计算支持并不好,对于SQL支持并不算好,而且对于Hive数仓的接管非常繁琐,全部手动实现 - Flink在实时计算方面是面向数据流[事件流]一个计算引擎
- SparkStreaming在实时计算方面是面向微批次一个计算引擎,有延迟
- Flink 起源于 Stratosphere 项目, Stratosphere 是在 2010~2014 年由 3 所地处柏林的大学和欧洲的一些其他的大学共同进行的研究项目, 2014 年 4 月 Stratosphere 的代码被复制并捐赠给了 Apache 软件基金会,参加这个孵化项目的初始成员是 Stratosphere 系统的核心开发人员, 2014 年 12 月, Flink 一跃成为 Apache 软件基金会的顶级项目。
- 在德语中, Flink 一词表示快速和灵巧,项目采用一只松鼠的彩色图案作为 logo,这不仅是因为松鼠具有快速和灵巧的特点,还因为柏林的松鼠有一种迷人的红棕色,而 Flink 的松鼠 logo 拥有可爱的尾巴,尾巴的颜色与 Apache 软件基金会的 logo 颜色相呼应,也就是说,这是一只Apache 风格的松鼠。
- Flink 虽然诞生的早(2010 年),但是其实是起大早赶晚集,直到 2015 年才开始突然爆发热度。
- 在 Flink 被 apache 提升为顶级项目之后, 阿里实时计算团队决定在阿里内部建立一个Flink 分支 Blink,并对 Flink 进行大量的修改和完善,让其适应阿里巴巴这种超大规模的业务场景。
- Blink 由 2016 年上线, 服务于阿里集团内部搜索、推荐、广告和蚂蚁等大量核心实时业务。 与 2019 年 1 月 Blink 正式开源,目前阿里 70%的技术部门都有使用该版本。
- Blink 比起 Flink 的优势就是对 SQL 语法的更完善的支持以及执行 SQL 的性能提升。
1 Flink 重要特点
1.1 事件驱动型(Event-driven)
- 事件驱动型应用是一类具有状态的应用,它从一个或多个事件流提取数据,并根据到来的事件触发计算、状态更新或其他外部动作。比较典型的就是以 kafka 为代表的消息队列几乎都是事件驱动型应用。
- 与之不同的就是 SparkStreaming 微批次
- 事件驱动型:
1.2 流与批的世界观
- 批处理的特点是有界、持久、大量,非常适合需要访问全套记录才能完成的计算工作,一般用于离线统计
- 流处理的特点是无界、实时, 无需针对整个数据集执行操作,而是对通过系统传输的每个数据项执行操作,一般用于实时统计。
- 在 spark 的世界观中,一切都是由批次组成的,离线数据是一个大批次,而实时数据是由一个一个无限的小批次组成的。
- 而在 flink 的世界观中,一切都是由流组成的,离线数据是有界限的流,实时数据是一个没有界限的流,这就是所谓的有界流和无界流。
- 无界数据流:
无界数据流有一个开始但是没有结束,它们不会在生成时终止并提供数据,必须连续处理无界流,也就是说必须在获取后立即处理 event。对于无界数据流我们无法等待所有数据都到达,因为输入是无界的,并且在任何时间点都不会完成。处理无界数据通常要求以特定顺序(例如事件发生的顺序)获取 event,以便能够推断结果完整性。 - 有界数据流:
有界数据流有明确定义的开始和结束,可以在执行任何计算之前通过获取所有数据来处理有界流,处理有界流不需要有序获取,因为可以始终对有界数据集进行排序,有界流的处理也称为批处理。 - 这种以流为世界观的架构,获得的最大好处就是具有极低的延迟。
1.3 分层 api
- 最底层级的抽象仅仅提供了有状态流,它将通过过程函数(Process Function)被嵌入到DataStream API 中。底层过程函数(Process Function) 与 DataStream API 相集成,使其可以对某些特定的操作进行底层的抽象,它允许用户可以自由地处理来自一个或多个数据流的事件,并使用一致的容错的状态。除此之外,用户可以注册事件时间并处理时间回调,从而使程序可以处理复杂的计算。
- 实际上,大多数应用并不需要上述的底层抽象,而是针对核心 API(Core APIs) 进行编程,比如 DataStream API(有界或无界流数据)以及 DataSet API(有界数据集)。这些 API 为数据处理提供了通用的构建模块,比如由用户定义的多种形式的转换(transformations),连接(joins),聚合(aggregations),窗口操作(windows)等等。 DataSet API 为有界数据集提供了额外的支持,例如循环与迭代。这些 API 处理的数据类型以类(classes)的形式由各自的编程语言所表示。
- Table API 是以表为中心的声明式编程,其中表可能会动态变化(在表达流数据时)。 TableAPI 遵循(扩展的)关系模型:表有二维数据结构(schema)(类似于关系数据库中的表),同时 API 提供可比较的操作,例如 select、 project、 join、 group-by、 aggregate 等。 TableAPI 程序声明式地定义了什么逻辑操作应该执行,而不是准确地确定这些操作代码的看上去如何 。 尽管 Table API 可以通过多种类型的用户自定义函数(UDF)进行扩展,其仍不如核心 API 更具表达能力,但是使用起来却更加简洁(代码量更少)。除此之外, Table API 程序在执行之前会经过内置优化器进行优化。
- 你 可 以 在 表 与 DataStream/DataSet 之 间 无 缝 切 换 , 以 允 许 程 序 将 Table API 与DataStream 以及 DataSet 混合使用。
- Flink 提供的最高层级的抽象是 SQL 。这一层抽象在语法与表达能力上与 Table API 类似,但是是以 SQL 查询表达式的形式表现程序。 SQL 抽象与 Table API 交互密切,同时 SQL 查询可以直接在 Table API 定义的表上执行
1.4支持有状态计算
- Flink 在 1.4 版本中实现了状态管理,所谓状态管理就是在流失计算过程中将算子的中间结果保存在内存或者文件系统中,等下一个事件进入算子后可以让当前事件的值与历史值进行汇总累计。
1.5 支持 exactly-once 语义
- 在分布式系统中,组成系统的各个计算机是独立的。这些计算机有可能 fail。
- 一个 sender 发送一条 message 到 receiver。根据 receiver 出现 fail 时 sender 如何处理 fail,可以将 message delivery 分为三种语义:
- At Most once:
对于一条 message,receiver 最多收到一次(0 次或 1 次).可以达成 At Most Once 的策略:sender 把 message 发送给 receiver.无论 receiver 是否收到 message,sender 都不再重发message. - At Least once:
对于一条 message,receiver 最少收到一次(1 次及以上).可以达成 At Least Once 的策略:sender 把 message 发送给 receiver.当 receiver 在规定时间内没有回复 ACK 或回复了 error信息,那么 sender 重发这条 message 给 receiver,直到 sender 收到 receiver 的 ACK. - Exactly once:
对于一条 message,receiver 确保只收到一次
1.6 支持事件时间(EventTime)
- 目前大多数框架时间窗口计算,都是采用当前系统时间,以时间为单位进行的聚合计算只能反应数据到达计算引擎的时间,而并不是实际业务时间
2 Flink操作环境上手
2.1 搭建Flink的工作环境
- 在pom.xml中加入依赖:
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_2.11</artifactId>
<version>1.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-streaming-scala-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_2.11</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 该插件用于将 Scala 代码编译成 class 文件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<!-- 声明绑定到 maven 的 compile 阶段 -->
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2.2 有界数据流处理(数据离线处理)
val environment:ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
val lineDataSet:DataSet[String] = environment.readTextFile("D:\\Resourse\\18_Flink\\datas\\input")
val wordDataSet:DataSet[String] = lineDataSet.flatMap(_.split(" "))
val wordAndNumDataSet:DataSet[(String,Int)] = wordDataSet.map((_,1))
// 0 代表以元组的第一个元素进行分组
val groupDataSet: GroupedDataSet[(String, Int)] = wordAndNumDataSet.groupBy(0)
//1 元组第二个元素进行求和
val aggrDataSet: AggregateDataSet[(String, Int)] = groupDataSet.sum(1)
aggrDataSet.print()
(hadoop,1)
(flink,2)
(hello,3)
(spark,3)
2.4 无界数据流(实时)处理示例
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val textDataStream: DataStream[String] = environment.socketTextStream("node102",54321)
val flatMapStream: DataStream[String] = textDataStream.flatMap(_.split(" "))
val mapDataStream:DataStream[(String,Int)] = flatMapStream.map((_,1))
val keyByDataStream: KeyedStream[(String, Int), Tuple] = mapDataStream.keyBy(0)
val reduceDataStream: DataStream[(String, Int)] = keyByDataStream.reduce((item1,item2) => (item1._1,item1._2 + item2._2))
reduceDataStream.print()
environment.execute()
[bduser@node102 ~]$ nc -lk 54321
123
a a a
a a a
aa
a a
a
a
a
a
a
a
aafdsf
sdf s
g
f
13224
3> (a,1)
1> (123,1)
3> (a,2)
1> (f,1)
2> (g,1)
3> (,1)
3> (a,3)
3> (,2)
1> (aafdsf,1)
3> (a,4)
3> (a,5)
1> (sdf,1)
3> (a,6)
3> (,3)
3> (a,7)
3> (a,8)
3> (,4)
3> (a,9)
3> (a,10)
3> (a,11)
3> (a,12)
3> (,5)
3> (a,13)
3> (s,1)
3> (13224,1)
3> (aa,1)
3> (,6)
3> (a,14)
3> (,7)
3> (,8)
3 集群环境搭建&Yarn模式
- 上传后解压
[bduser@node102 softwares]$ tar -zxvf flink-1.7.0-bin-hadoop27-scala_2.11.tgz -C /opt/modules
- 配置:conf/flink-conf.yaml
- slaves
写入节点的IP地址 - 分发
- 环境变量 root -> etc/profile
#FLINK_HOME
export FLINK_HOME=/opt/modules/flink-1.7.0
export PATH=$PATH:$FLINK_HOME/bin
- 分发后重启(使得环境变量生效)
- 启动
[bduser@node102 ~]$ xcall echo $FLINK_HOME
----------------node102-------------------
/opt/modules/flink-1.7.0
----------------node103-------------------
/opt/modules/flink-1.7.0
----------------node104-------------------
/opt/modules/flink-1.7.0
[bduser@node102 ~]$ start-cluster.sh
Starting cluster.
Starting standalonesession daemon on host node102.
Starting taskexecutor daemon on host node102.
Starting taskexecutor daemon on host node103.
Starting taskexecutor daemon on host node104.
[bduser@node102 ~]$ xcall jps
----------------node102-------------------
2353 Jps
1800 StandaloneSessionClusterEntrypoint
2267 TaskManagerRunner
----------------node103-------------------
1681 Jps
1656 TaskManagerRunner
----------------node104-------------------
1601 Jps
1576 TaskManagerRunner
- 搭建完成
- 运行一个程序
编写好程序后打成jar包发布到集群
val paramTool: ParameterTool = ParameterTool.fromArgs(args)
val host = paramTool.get("host")
val port = paramTool.getInt("port")
val outputPath = paramTool.get("output")
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val textDataStream: DataStream[String] = environment.socketTextStream(host, port)
val flatMapStream: DataStream[String] = textDataStream.flatMap(_.split(" "))
val mapDataStream: DataStream[(String, Int)] = flatMapStream.map((_,1))
val keyByDataStream: KeyedStream[(String, Int), Tuple] = mapDataStream.keyBy(0)
val reduceDataStream: DataStream[(String, Int)] = keyByDataStream.reduce((item1,item2)=>(item1._1, item1._2+item2._2)).setParallelism(10)
reduceDataStream.writeAsText(outputPath).setParallelism(1)
environment.execute()
val paramTool: ParameterTool = ParameterTool.fromArgs(args)
val inputPath = paramTool.get("input")
val outputPath = paramTool.get("output")
val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
val lineDataSet: DataSet[String] = environment.readTextFile(inputPath)
val wordDataSet: DataSet[String] = lineDataSet.flatMap(_.split(" "))
val wordAndNumDataSet: DataSet[(String, Int)] = wordDataSet.map((_,1))
val groupDataSet: GroupedDataSet[(String, Int)] = wordAndNumDataSet.groupBy(0)
val aggrDataSet: AggregateDataSet[(String, Int)] = groupDataSet.sum(1)
aggrDataSet.writeAsCsv(outputPath, ",").setParallelism(1)
environment.execute()
[bduser@node102 datas]$ flink run -c com.nefu.flink.test.DataSetWordCount /home/bduser/HelloFlink-1.0-SNAPSHOT.jar --input /home/bduser/datas/space.txt --output ~/finkoutput
Starting execution of program
[bduser@node102 datas]$ flink run -c com.nefu.flink.test.DataSetWordCount /home/bduser/HelloFlink-1.0-SNAPSHOT.jar --input /home/bduser/datas/space.txt --output ~/finkoutput
[bduser@node102 ~]$ cat finkoutput
-,1,echo,2,flink,1,hello,2,spark,1,[
Yarn模式
[bduser@node102 ~]$ start-yarn.sh
starting yarn daemons
starting resourcemanager, logging to /opt/modules/hadoop-2.7.6/logs/yarn-bduser-resourcemanager-node102.out
node102: starting nodemanager, logging to /opt/modules/hadoop-2.7.6/logs/yarn-bduser-nodemanager-node102.out
node104: starting nodemanager, logging to /opt/modules/hadoop-2.7.6/logs/yarn-bduser-nodemanager-node104.out
node103: starting nodemanager, logging to /opt/modules/hadoop-2.7.6/logs/yarn-bduser-nodemanager-node103.out
[bduser@node102 ~]$ yarn-session.sh -n 2 -s 2 -jm 1024 -tm 1024 -nm flinkdemo -d
其中:
-n(–container): TaskManager 的数量。
-s(–slots): 每个 TaskManager 的 slot 数量,默认一个 slot 一个 core,默认每个taskmanager 的 slot 的个数为 1,有时可以多一些 taskmanager,做冗余。
-jm: JobManager 的内存(单位 MB)。
-tm:每个 taskmanager 的内存(单位 MB)。
-nm: yarn 的 appName(现在 yarn 的 ui 上的名字)。
-d:后台执行。
- 在执行一次任务:
[bduser@node102 ~]$ flink run -c com.nefu.flink.test.DataSetWordCount /home/bduser/HelloFlink-1.0-SNAPSHOT.jar --input /home/bduser/datas/space.txt --output ~/finkoutput
- yarnUI界面端口号:8088
- flinkUI端口号:8081