Flink原理简介和使用
原创
©著作权归作者所有:来自51CTO博客作者wx58c2b58cac641的原创作品,请联系作者获取转载授权,否则将追究法律责任
任务调度原理
客户端不是运行时和程序执行的一部分
但它用于准备并发送dataflow(JobGraph)给Master(JobManager)
然后客户端断开连接或维持连接以等待接受计算结果
当Flink集群启动后 首先会启动一个JobManager和一个或多个TaskManager去执行
然后TaskManager将心跳和统计信息汇报给JobManager
TaskManager之间以流的形式进行数据传输
以上三者均为独立的JVM进程
提交Job的客户端
可以是运行在任何机器上 (与JobManager环境连通即可)
提交Job后 Clinet可以结束进程(Streaming的任务)
也可以不结束并等待结果返回
主要负责调度Job并协调Task做checkpoint
职责很像Storm的Nimbus
从Clinet处接受Job和Jar包等资源后 会生成优化后的执行计划
并以Task的单元调度到各个TaskManager去执行
TaskManager启动的时候就设置好了槽位数(Slot)
每个Slot能启动一个Task 。Task为线程
从JobManager处接受需要部署的Task
部署启动后 与自己上游建立Netty连接 接受数据并处理
TaskManager与Slots
- Flink中的每一个worker(TaskManager)都是一个JVM进程
它可能会在独立的线程上执行一个或多个subtask
- 控制一个worker能接受多少个task ,worker通过task slot来进行控制
- 每个task slot表示TaskManager拥有资源的一个固定大小的子集
假如一个TaksManager有三个slot 那么它会将其管理的内存分成三份分给三个slot
- 资源slot化意味着一个subtask将不需要跟来自其他job的subtask竞争被管理的内存 取而代之的是它将拥有一定数量的内存储备
需要注意的是 这里不会涉及到CPU的隔离 slot目前仅仅用来隔离task受管理的内存
- 通过调整task slot的数量 允许用户定义subtask之间如何互相隔离
如果一个taskManager一个slot 那将意味着每个task group运行在独立的JVM中 (该JVM可能通过一个特定的容器启动的)
而一个TaskManager多个slot意味着更多的subtask可以共享同一个JVM
而同一个JVM进程中的task将共享TCP连接(基于多路复用)和心跳消息
它们也可能共享数据集和数据结构 因此这减少了每个task的负载
子任务共享slot
即使它们是不同任务的子任务(前提是它们来自同一个job)
这样的结果是 一个slot可以保存业务的整个管道
- Task slot是静态概念 是指TaskManager具有并发执行能力
可以通过taskmanager.numberOfTaskSlots进行配置
- 并行度 parallelism是动态概念 即每个TaskManager运行程序时实际使用的并发能力
可以通过参数 parallelism.default进行配置
假设一共有3个TaskManager 每个TaskManager中分配3个TaskSlot
也就是说每个TaskManager可以接收3个task
一共9个TaskSlot 如果设置parralelism.default=1即运行程序默认的并行度为1
9个TaskSlot只用了一个 有8个空闲
因此需要设置合适的并行度才能提高效率
程序和数据流
- 所有的Flink由三部分组成 Source、Transformation、Sink
Source负责读取数据源
Transformation利用各种算子进行处理加工
Sink负责输出
在运行时 Flink上运行的程序会被映射成 "逻辑数据流" dataflows 它包含了这三部分
每一个dataflow以一个或多个sources开始
以一个或多个sinks结束
在大部分情况下 程序的转换算法(transformations)跟dataflow中的算子(operator)是一一对应关系
但有时候一个transformations可能对应多个operator
执行图(ExecutionGraph)
由Flink程序直接映射成数据流图 StreamGraph 也被成为逻辑流图 因为它们表示的是计算逻辑的高级视图
为了执行一个流处理程序 Flink需要将逻辑流图转换为物理数据流图(也叫执行图)详细说明程序的执行方式
StreamGraph -> JobGraph -> ExecutionGraph -> 物理执行图
是根据用户通过Stream API编写的代码生成的最初的图 用来表示程序的拓扑结构
StreamGraph经过优化后生成了JobGraph 提交给JobManager的数据结构
主要优化为:
将多个符合条件的节点 chain在一起作为一个节点 这样可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗
JobManager根据JobGraph生成了ExecutionGraph
ExecutionGraph是JobGraph的并行化版本 是调度层最核心的数据结构
JobManager根据ExecutionGraph对Job进行调度后
在各个TaskManager上部署Task后形成的“图”
并不是一个具体的数据结构
并行度(Parallelism)
Flink程序的执行具有并行、分布式特性
一个流包含了一个或多个分区(stream partition)
而每一个算子(operator)可以包含一个或多个子任务(operator subtask)
这些子任务在不同的线程、不同物理机或不同的容器中不依赖的执行
一个特定算子的子任务(subtask)的个数被称为并行度
一般情况下 一个流程序的并行度 可以认为其所有算子中最大的并行度
一个程序中 不同的算子可能具有不同的并行度
Stream在算子之间传输数据的形式可以是 one-to-one(forwarding)的模式也可以是redistributing的模式 具体是哪一种形式 取决于算子的种类
one-to-one:
stream(比如在source和map operator之间) 维护着分区以及元素的顺序 那意味着map算子的子任务看到的元素的个数以及顺序跟source算子的子任务生产的元素的个数、顺序相同 map、filter、flatMap等算子都是one-to-one的对应关系
Redistributing:
stream(map()跟keyBy/window之间或者keyBy/window跟slink之间)的分区会发生改变
每一个算子的子任务依据所选择的transformation发送数据到不同的目标任务
例如:
keyBy()基于hashCode重分区
broadcast和rebalance会随机重新分区
这些算子都会引起redistribute过程
该过程就类似于spark中的shuffle
类似于spark的窄依赖、宽依赖
任务链
a 、
Flink采用一种成为任务链的优化技术
可以在指定条件下减少本地通信开销
为了满足任务链的要求
必须将两个或多个算子设为相同的并行度
并通过本地转发的方式连接(local forward)
b、
相同并行度one to one操作 Flink这样相连的算子链接在一起形成一个task 原来的算子成为里面的subtask
c、
并行度相同、并且是One-to-One操作 两个条件缺一不可
d、
将算子链接成task是非常有效的优化
它能减少线程之间的切换和基于缓存区的数据交换
在减少时延的同时提升吞吐量
链接的行为可以在编程API中进行指定
上面是纯理论 下面实践下 才能对理论理解的更加透彻
Flink 流处理API
Environment
创建一个执行环境 表示当前执行程序的上下文
如果程序是独立调用的 则此方法返回本地执行环境
如果从命令行调用程序以提交到集群 则此方法返回集群的执行环境
val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
或
val env = StreamExecutionEnvironment.getExecutionEnvironment
a 返回本地执行环境 需要在调用时指定默认的并行度
val env = StreamExecutionEnvironment.createLocalEnvironment(1)
b 返回集群环境 将jar包提交到远程服务器 需要在调用时指定 JobManager的IP和端口号 并指定要在集群中运行的jar包
val env = ExecutionEnvironment.createRemoteEnvironment("jobmanage-hostname",
6123,"YOURPATH//wordcount.jar")
如果没有设置并行度 会以flink-conf.yaml中配置为准 默认是1
Source
// 定义样例类,传感器 id ,时间戳,温度
case class SensorReading(id: String, timestamp: Long, temperature: Double)
object Sensor {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
val stream1 = env
.fromCollection(List(
SensorReading("sensor_1", 1547718199, 35.80018327300259),
SensorReading("sensor_6", 1547718201, 15.402984393403084),
SensorReading("sensor_7", 1547718202, 6.720945201171228),
SensorReading("sensor_10", 1547718205, 38.101067604893444)
))
stream1.print("stream1:").setParallelism(1)
env.execute()
}
}
val stream2 = env.readTextFile("YOUR_FILE_PATH")
需要引入kafka连接器的依赖 pom:
<dependency>
<groupId>org.apache.flink</ groupId>
<artifactId>flink-connector-kafka-0.11_2.11</artifactI d>
<version>1.7.2</version>
</dependency>
代码:
val properties = new Properties()
properties.setProperty("bootstrap.servers", "localhost:9092")
properties.setProperty("group.id", "consumer-group")
properties.setProperty("key.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer")
properties.setProperty("value.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer")
properties.setProperty("auto.offset.reset", "latest")
val stream3 = env.addSource(new FlinkKafkaConsumer011[String]("sensor", new
SimpleStringSchema(), properties))
- 先测试下kafka发送消息 从zk中消费消息是否可以
cd /opt/kafka/kafka_2.10-0.8.2.1/bin
生产消息
./kafka-console-producer.sh --broker-list 192.168.84.128:9092 --topic test
消费消息
./kafka-console-consumer.sh --zookeeper 192.168.84.128:2181 --topic test --from-beginning
demo程序是用的kafka版本是 kafka-0.11_2.11
目前虚拟机上安装的版本是2.10-0.8.2.1 所以为了跑demo程序 所以安装下kafka-0.11_2.11版本
安装过程大数据处理工具Kafka、Zk、Spark
https://archive.apache.org/dist/kafka/0.11.0.1/kafka_2.12-0.11.0.1.tgz
/opt/kafka/版本号/config/server.properties 这个配置文件
配置zk集群的配置项名称
kafka_2.10-0.8.2.1: zookeeper.contact
kafka_2.12-0.11.0.1: zookeeper.connect
./kafka-console-producer.sh --broker-list 192.168.84.128:9092 --topic test
./kafka-console-consumer.sh --zookeeper 192.168.84.128:2181 --topic test --from-beginning
代码
https://gitee.com/pingfanrenbiji/Flink-UserBehaviorAnalysis/blob/master/FlinkTutorial/src/main/scala/com/xdl/apitest/SourceTest.scala
消费成功