Flink 的核心语义和架构模型
Flink核心概念
- Stream(流):Flink中的流是无边界的,流数据随着时间的增加而增长,计算不停止。
- State(状态):state是对数据某个时间段的状况描述。比如说在Storm中是无状态的,因为storm是强依赖于zookeeper,其将数据都以日志的形式存储在zookeeper中,所以讲是无状态的形式。而在Flink中,state占据很重要的地位,确保Exactly-one(实际一次)语义需要将数据写到状态中,以及集群出现Fail-over(失败转移)时,需要持久化的状态来作为备用机的自动重启的前提条件。
- Time(时间):time是Flink程序开发时用来判断业务状态是否滞后和延迟的重要依据。Flink支持Event time、Ingestion time、Processing time 等多种时间语义。
- API:Flink自身提供了不同级别的抽象来支持我们开发流式或者批量处理程序,由上而下可分为 SQL / Table API、DataStream API、ProcessFunction 三层。
Flink 编程模型和流式处理
Flink的基础模块是Stream(流)和Transformations(转换),每个数据流起始于Source,结束于Sink。数据流类似于有向无环图(DAG)。
这整个流程称为Streaming DataFlow。
在分布式运行的环境中,Flink提出算子链的概念,即将多个算子放在同一个线程(task)中执行,这样的优点是:
- 减少了线程之间的切换
- 避免部分消息的序列化和反序列化
- 避免数据在缓冲区的交换
-
Flink 集群模型和角色
在集群环境中,Flink包含两类进程。
JobManager:集群管理者的角色,负责任务的调度、协调checkpoints、协调故障恢复、收集Job的状态信息,并且管理从节点TaskManager。
- TaskManager:负责执行计算的Worker,在其上执行Flink Job的一组Task。TaskManager也是当前节点的管理者,负责把该节点的服务器信息,比如内存,磁盘,任务运行情况等向JobManager汇报。
- Client:用户提交编写的Flink工程时,会先创建一个客户端进行提交,Client 会根据用户传入的参数选择使用 yarn per job 模式、stand-alone 模式还是 yarn-session 模式将 Flink 程序提交到集群。
Flink 资源和资源组
在Flink中,一个TaskManager就是一个JVM进程,而且其中执行的每个task都是一个单纯的线程。
Flink提出了Slot的概念来控制TaskManager的Task的数量,每个Task都在不同的Slot中执行,这样避免线程之间资源竞争,但是Slot仅仅是对内存进行隔离,对CPU是不起作用的,因此在同一个TaskManager中的Task可以共享TCP连接,这样节省了网络的传输,间接提高了程序的执行效率,降低资源的消耗。
Flink还允许不能形成算子链的两个操作放在同一个TaskSlot中达到资源共享的目的。
Flink 的优势及与其他框架的区别
架构
Stom 的架构是经典的主从模式,并且强依赖 ZooKeeper;Spark Streaming 的架构是基于 Spark 的,它的本质是微批处理,每个 batch 都依赖 Driver,我们可以把 Spark Streaming 理解为时间维度上的 Spark DAG。
Flink 也采用了经典的主从模式,DataFlow Graph 与 Storm 形成的拓扑 Topology 结构类似,Flink 程序启动后,会根据用户的代码处理成 Stream Graph,然后优化成为 JobGraph,JobManager 会根据 JobGraph 生成 ExecutionGraph。ExecutionGraph 才是 Flink 真正能执行的数据结构,当很多个 ExecutionGraph 分布在集群中,就会形成一张网状的拓扑结构。
容错
Storm 在容错方面只支持了 Record 级别的 ACK-FAIL,发送出去的每一条消息,都可以确定是被成功处理或失败处理,因此 Storm 支持(at-least-once)至少处理一次语义。
针对以前的 Spark Streaming 任务,我们可以配置对应的 checkpoint,也就是保存点。当任务出现 failover 的时候,会从 checkpoint 重新加载,使得数据不丢失。但是这个过程会导致原来的数据重复处理,不能做到“只处理一次”语义。
Flink 基于两阶段提交实现了精确的一次处理语义,我们将会在后面的课时中进行完整解析。
反压(BackPressure)
反压是分布式处理系统中经常遇到的问题,当消费者速度低于生产者的速度时,则需要消费者将信息反馈给生产者使得生产者的速度能和消费者的速度进行匹配。
Stom 在处理背压问题上简单粗暴,当下游消费者速度跟不上生产者的速度时会直接通知生产者,生产者停止生产数据,这种方式的缺点是不能实现逐级反压,且调优困难。设置的消费速率过小会导致集群吞吐量低下,速率过大会导致消费者 OOM。
Spark Streaming 为了实现反压这个功能,在原来的架构基础上构造了一个“速率控制器”,这个“速率控制器”会根据几个属性,如任务的结束时间、处理时长、处理消息的条数等计算一个速率。在实现控制数据的接收速率中用到了一个经典的算法,即“PID 算法”。
Flink 没有使用任何复杂的机制来解决反压问题,Flink 在数据传输过程中使用了分布式阻塞队列。我们知道在一个阻塞队列中,当队列满了以后发送者会被天然阻塞住,这种阻塞功能相当于给这个阻塞队列提供了反压的能力。