Flink流式计算|青训营笔记

325 阅读19分钟

这是我参与「第四届青训营 」笔记创作活动的第二天

Flink流式计算

Flink是目前最主流的流式计算引擎,下面我们主要如下几个部分来对Flink进行介绍:Flink概述、Flink架构、Flink流批一体化、Flink容错机制、Flink窗口函数实现。

Flink概述

  1. Apache Flink诞生背景
  • 大数据诞生:无法在一定时间内使用常规软件工具对其进行获取、存储、管理和处理的数据集合。大数据具有4V特点:Volume(海量化)、Velocity(快速化)、Variety(多样性)、Value(价值化)——大数据是海量快速增长且价值密度低的多样化数据
  • 大数据技术进程:大数据经历史前阶段-离线计算-批处理-流式计算几个阶段,最终进入流式计算时代

image.png

  • 为什么需要流式计算?大数据具有低密度价值的特点,数据量很大,价值也很大,但是价值密度很低,而大数据实时性带来的价值更大,比如说:监控场景、金融分控、实时推荐等。
  1. Apache Flink优势
  • 流式计算引擎对比 目前Flink在一致性保证,延迟,吞吐,容错,支持等方面都有一定优势。

image.png

  • Flink技术特点:Flink具有窗口等高阶需求支持,流批一体而且很不错的容错机制(状态容错,Exactly-Once)等特点

image.png

  1. Apache Flink开源生态圈 Flink目前支持大部分关系型数据库、消息中间件、大数据存储作为数据源,内部提高SQL、DataStream、pyFlink等方式来进行计算开发。同时Flink还支持主流的k8s进行资源管理,上层还是支持FlinkML等进行机器学习相关计算。

image.png

image.png

Flink架构

  1. Flink分层架构 从下到上Flink主要分层资源调度层、状态存储层、执行引擎(Runtime)层以及SDK层
  • 资源调度层:Flink可以支持部署多种环境(Standalone、K8s、Yarn)
  • 状态存储层:负责存储算子的状态信息
  • 执行引擎层:执行引擎层提供统一的DAG API,用于描述数据处理的Pipeline,无论是流还是批,都会转化为DAG图,调用层再将DAG转换为分布式环境下的Task,Task之间通过Shfulle传输数据
  • SDK层:Flink的SDK目前只支持三类,SQL/Table(表数据)、DataStream、Python(pyflink)

image.png

  1. Flink整体架构
  • 整体架构:一个Flink集群,主要包含一下两个核心组件:JobManager(JM),负责整个任务的协调工作包括:调度task、触发协调Task做Checkpoint、协调容错恢复等 image.png
  • JobManager:协调 Flink 应用程序的分布式执行。它决定何时调度下一个 task(或一组 task)、对完成的 task 或执行失败做出反应、协调 checkpoint、并且协调从失败中恢复等等。这个进程由三个不同的组件组成:Dispatcher:提供了一个 REST 接口,用来提交 Flink 应用程序执行,并为每个提交的作业启动一个新的 JobMaster。它还运行 Flink WebUI 用来提供作业执行信息。JobMaster:负责管理单个JobGraph的执行,管理一个job的整个生命周期,会向ResourceManager申请slot,并调度task任务到对应的TM上。Flink 集群中可以同时运行多个作业,每个作业都有自己的 JobMaster。ResourceManager: 负责 Flink 集群中的资源提供、回收、分配 Task manager拉起之后会向RM注册。 它管理 task、slots,这是 Flink 集群中资源调度的单位。Flink 为不同的环境和资源提供者(例如YARN、Kubernetes 和 standalone 部署)实现了对应的 ResourceManager。 image.png
  • TaskManager:执行作业流的 task,并且缓存和交换数据流。每一TaskManager都是JVM进程,并且可以在独立的线程中执行一个或者多个任务。要控制接受任务数量,可以调节slot(至少一个)。在TaskManager 中资源调度的最小单位是 task slot。TaskManager 中 task slot 的数量表示并发处理 task 的数量。对于分布式执行,Flink 将算子的 subtasks 链接成 tasks,每个task由一个线程执行。将算子链接成 task 是个有用的优化:它减少线程间切换、缓冲的开销,并且减少延迟的同时增加整体吞吐量。分配资源意味着subtask不会与其他作业的subtask竞争托管内存,注意此处没有 CPU 隔离。 image.png
  1. Flink架构优化
  • 流/批/OLAP支持

三个业务场景特点

流式计算批式计算交互式分析
实时计算离线计算OLAP
延迟在秒级以内处理时间为分钟到小时级别甚至天级别处理时间秒级
0 ~ 1s10s ~ 1h+1 ~ 10s
广告推荐、金融分控推荐引擎构建索引、批式数据分析数据分析BI报表

三种业务场景的解决方案挑战

模块流式计算批式计算交互式分析(OLAP)
SQLYesYesYes
实时性高、处理延迟毫秒级别高、查询延迟在秒级,且要求高并发查询
容错能力中,大作业失败重跑代价高No,失败重跑即可
状态NoNo
准确性Exactly Once,要求高,重跑需要恢复状态Exactly Once,失败重跑即可Exactly Once,失败重跑即可
扩展性YesYesYes

image.png

三种业务场景可以用一套引擎解决吗?

批式计算是流式计算的特例,Everything is Streams,有界数据集(批式数据)也是一种数据流、特殊的数据流;OLAP计算是一种特殊的批式计算,它的并发和实时性要求比较高,其他情况比普通批式作业没有特别大区别。

待解决问题

Btach场景需求OLAP场景需求
流批一体支持短查询作业场景
1. Unify DataStream API1. 高并发支持
2. Scheduler2. 极致处理性能
3. Shuffle Service
4. Failover Recovery

image.png

Apache Flink 最终架构设想:

image.png

  1. Flink作业示例
  • WordCount示例,从kafka中读取一个实时数据流,没10s统计一次单词出现次数,实现代码如下:

image.png

  • 将业务逻辑转换为一个Streaming DataFlow Graph

image.png

  • 假设作业的sink算子的并发配置为1,其他算子并发为2将上面的Streaming DataFlow Graph转为Parallel Dataflow(内部叫Execution Graph)

image.png

  • 为了更有效地分布式执行,Flink会尽可能地将不同地operator链接(chain)在一起形成SubTask。每个SubTask可以在一个线程中执行,内部叫做Operator Chain,如下图的source和map算子可以chain在一起 image.png

  • 最后将上面的Task调度到具体的TaskManager中的slot执行,一个Slot只能运行同一个task的SubTask

image.png

Flink流批一体化

  1. 为什么要流批一体化
  • 业务需求:比如说我们业务需要实时统计一个短视频的播放量、点赞数,实时观看人数等;同时还需要按天统计创作者的一些数据信息
  • 架构痛点:人工成本比较高:批、流两套系统,相同逻辑需要开发两遍;数据链路冗余:本身计算内容是一致的,由于是两套链路,相同的逻辑需要运行两遍,造成一定的资源浪费;数据口径不一致:两套系统,两套算子,两套UDF,通常会产生不同程度的误差,这些误差会给业务带来非常大的困扰; image.png
  1. 流批一体的挑战
  • 流批业务场景特点不同
流式计算批式计算
实时计算离线计算
延迟在秒级以内处理时间为分钟到小时级别,甚至天级别
0 ~ 1s10s ~ 1h+
广告推荐、金融分控搜索引擎构建索引、批式数据分析
  • 批式计算与流式计算核心区别
维度流式计算批式计算
数据流无限数据流有限数据流
时延低延迟、业务会感知运行中的情况实时性要求不高,会关注最终结果产出时间
  1. Flink如何做到流批一体
  • 批式计算是流式计算的特例,Everything is Streams,有界数据流(批式数据)也是一种数据流、一种特殊的数据流;理论上使用一套引擎架构来解决流式计算和批式计算,只是对不同场景支持相应扩展性,并允许做不同的优化策略。

image.png

  • Flink在架构设计上针对SQL层、DataStream API层、调度(Scheduler)层、Shuffle Service均完成对流和批的支持。 image.png
  1. Flink模块对流批一体的支持
  • DataStream API层统一化,流批都可以使用DataStream API来开发;
  • Scheduler调度层架构统一,支持流批场景; Flink1.2之前支持一下两种调度模式: | 模式 | 特点 |场景| | --- | --- | --- | | EAGER | 申请一个作业所有资源,然后调度所有Task,所有Task之间采用Pipeline的方法进行通信 |Stream作业场景| | LAZY | 先调度上游,等待上游产生数据或结束后在调度下游,类似Spark的stage执行模式 | Batch作业场景 |

Pipeline Region 调度

Pipleline的数据交换方式连接的Task构成一个Pipeline Regin;不管是流作业还是批作业,都是按照Pipleline Region粒度来申请资源和调度任务。(ALL_EDGES_BLOCKING,ALL_EDGES_PIPELINED) image.png

  • Shuffle Service层对流批一体支持 Shuffle概念:在分布式计算中,用于连接上下游数据交互(衔接)的过程

Shuffle实现:基于文件的Pull Based Shuffle,比如Spark或MR,它的特点是具有较高的容错性,适合较大规模的批处理作业,由于是基于文件的,他的容错性和稳定性会更好一些;基于Pipeline的Push Based Shuffle,比如Flink、Storm、Presto等,它的特点是低延迟和高性能,但是由于因为shuffle数据没有存储下来,如果是batch任务的化,就需要进行重跑恢复;

流和批Shuffle之间的差异Shuffle数据的生命周期:流作业的Shuffle数据与Task是绑定的,而批作业的Shuffle数据与Task是解耦的;Shuffle数据存储介质:流作业生命周期比较短,而且流作业为了实时性,Shuffle通常存储在内存中,批作业因为数据量比较大以及容错的需求,一般会存储在磁盘里;Shuffle的部署方式:流作业Shuffle服务和计算节点部署在一起,可以减少网络开销,从而减少latency,而批作业不同;

Flink流批一体实现:在Streaming和OLAP场景,为了性能需要,通常使用基于Pipeline的Shuffle(一个pipeline region);在Batch场景:一般使用Blocking的Shuffle模式;Flink为了统一在Streaming和Batch模式下的Shuffle架构,Flink实现了一个Puggable的Shuffle Service框架,抽象出一些公共模块;Shuffle Service Flink开源社区的支持——Netty Shuffle Service,Remote Shuffle Service(pipeline模式走remote性能反而下降) image.png

Flink容错机制

  1. 数据流与动态表
  • 传统SQL与流处理
特征SQL流处理
处理数据的有界性处理的表是有界的流是一个无界元组序列
处理数据的完整性执行查询可以访问完整的数据执行查询无法访问所有的数据
执行时间批处理查询产生固定大小结果后终止查询不断更新结果,永不终止
  • 数据流与动态表的转换

image.png 流——>表:动态表与表示批处理数据的静态表不同,动态表是随着时间改变的; image.png 动态表->动态表:查询永不终止,查询结果会不断更新,产生新的动态表。需要存储每一个用户的URL计数(状态),以便增加该计数并在输入表接收新行时发送新结果。 image.png

  • 总结:数据流和动态表之间转化,在数据流的查询不会终止,查询可能会有状态,用来不断更新查询的结果。查询结果出现故障怎么办?Flink容错机制
  1. Flink容错机制-语义
  • 不同数据处理保证的语义

At-most-once:出现故障的时候,啥也不做。数据处理不保证任何语义,处理时延低;

At-least-once:保证每条数据均至少被处理一次,一条数据可能存在重复消费;

Exactly-once:最严格的处理语义,从输出结果来看,每条数据均被消费且仅被消费一次,仿佛故障从而发生。 image.png

  1. Flink容错机制-checkpoint(快照)
  • 状态快照 JobManager负责统一制作快照(Checkpoint);状态恢复的时间点:等待所有处理逻辑消费完成source保留状态及之前的数据;简单快照制作算法:暂停处理输入数据,等待后续所有处理算子消费当前输入数据,作业所有算子复制自己的状态并保存到远端可靠存储。 image.png
  • 状态恢复 从Storage中取出Checkpoint进行状态恢复 image.png
  • Chandy-Lamport算法(异步快照) 背景:2个Source,2个sink,一个做奇数加法,一个做偶数加法。 image.png 快照制作开始:每个Source算子都收到JobManager发送的Checkpoint Barrier标识状态快照制作开始。 image.png Source算子处理:各个source保存自己状态后,向所有连接的下游继续发送Checkpoint Barrier,同时告知JM自己状态已经制作完成。 image.png Barrier Alignment:算子会等待所有上游的barrier到达后猜开始快照制作;已经制作完成上游算子会继续处理数据,并不会被下游算子制作快照工程堵塞。(ps:上游算子会向所有连接的下游算子传递barrier,下游算子收到所有连接的上游barrier开始制作快照,如有部分上游算子速度快,提前到达的处理数据进行缓存,不进行计算。) image.png checkpoint结束:所有算子都告知JM状态制作完成后,整个Checkpoint结束。 image.png
  • 总结

解耦快照和数据处理过程,各个算子制作完成状态快照后就正常处理数据,不用等下游算子制作快照完成;在快照制作和Barrier Alignment过程中需要暂停处理数据,仍然会增加数据处理延迟;快照保存到远端可能极为耗时;

  1. Flink容错机制-两阶段提交(端到端的Exactly-once)
  • 端到端的Exactly-once语义

checkpoint能保证每条数据都对各个有状态的算子更新一次,sink输出算子仍然可能下发重复的数据;严格意义的端到端的Exactly-once语义需要特殊的sink算子实现;

  • 两阶段提交协议

在多个节点参与执行的分布式系统中,为了协调每个节点都能同时执行或者回滚事务性的操作,引入一个中心节点统一处理所有节点的执行逻辑,中心节点叫做协作者,被中心节点调度的其他业务节点叫做参与者;

预提交阶段:协作者向所有参与者发送一个commit信息;每个参与的协作者收到信息后,执行事务,但是不真正提交;若事务成功执行完成,发送一个成功的消息(vote yes),执行失败,则发送一个失败的消息(vote no);

提交阶段-正常:(协作者成功接收到所有参与者vote yes消息)协作者向所有参与者发送一个commit信息;每个收到commit消息的参与者释放执行事务所需的资源,并结束这次事务的执行;完成前一步操作,参与者发送一个ack消息给协作者;协作者收到所有参与者的ack信息后,标识该事务执行完成。

提交阶段-异常回滚:(协作者又收到参与者vote no的消息,或者发生等待超时)协作者向所有参与者发送一个rollback信息;每个收到rollback消息的参与者回滚事务的执行操作,并释放事务所占资源;完成前一步,参与者发送ack信息给协作者;协作者收到所有参与者的ack信息后,标识该事务成功完成回滚; image.png

  • Flink中的2PC sink Job Manaager作为协作者,各个算子作为参与者 image.png 开始制作快照 image.png 制作快照的过程中就开启预提交commit image.png 快照制作完成和提交是同步的 image.png

  • Flink两阶段提交总结

事务开启:在sink task向下游写数据之前,均开启一个事务,后续所有写数据的操作均在这个事务中执行,事务未提交前,事务写入的数据下游不可读。

预提交阶段:JobManager开始下发checkpoint Barrier,当各个处理逻辑接收到barrier停止处理后续数据,对当前状态制作快照,此时sink也不再当前事务下继续处理数据(为后续的数据需要新打开一个事务)。状态制作成功则向JM成功的消息,失败则发送失败的信息。(基本常驻的,每次barrier的数据开启一个事务)

提交阶段:若JM收到所有预提交成功的信息,则向所有处理逻辑(包括sink)发送可以提交此次事务的信息,sink接收到此信息后,则完成此次事务的提交,此时下游可以读到此次事务写入的数据;若JM有收到预提交失败的信息,则通知所有处理逻辑回滚这次事务的操作,此时sink则放弃这次事务提交的数据下。

流式计算Window计算-以Flink为例

  1. 基础知识点
  • 处理时间 VS 事件时间 处理时间:数据在流式计算系统中真正处理所在机器的当前时间;事件时间:数据产生的时间,如客户端、传感器、后端代码等上报数据时的时间 image.png

  • 事件时间窗口 实时计算:事件时间窗口;数据实时进入到真实时间发生的窗口进行计算,可以有效的处理数据延迟和乱序。(何时关闭窗口?) image.png

  • Watermark 在数据中插入watermark,来表示当前的真实时间,它可以用来在乱序容错和实时性之间做一个平衡。 image.png

  1. Watermark
  • 定义

表示系统认为的当前真实的事件时间

Watermark =进入Flink的最大事件时间(mxtEventTime)-指定的延迟时间(t)

如果有窗口的停止时间等于或者小于maxEvntTime-t(当时的watermark)窗口被触发执行

  • watermark场景 有序Stream的watermark:如果数据元素的事件事件是有序的,watermark时间戳会随着数据元素事件按时间顺序生成,所以watermark=maxtime-0=maxtime image.png 乱序事件中的Watermark:现实情况下数据元素往往并不是按照其产生顺序接入到Flink 系统中进行处理,而频繁出现乱序或迟到的情况,则需要使用 Watermarks 。比如下图,设置延迟时间t为2 image.png 并行数据流中的Watermark:再多并行度的情况下,Watermark会有一个对齐机制,这个对齐机制会取所有channel中最小的matermark image.png
  • 使用 SQL
CREATE TABLE Orders (
    user BIGINT,
    product STRING,
    order_time TIMESTAMP(3),
    WATERMARK FOR order_time AS order_time - INTERVAL '5'SECOND
    ) WITH ( ...);

DataStream

WatermarkStrategy
        .<Tuple2<Long, String>>forBoundedOutOfOrderness(Duration.ofSeconds(20))
        .withTimestampAssigner((event, timestamp)->event.f0);
  • Watermark传递 Per-partition watermark:基于每个partition单独的watermark生成机制,可以避免多个partition之间数据读取加剧乱序程度;partition/subtask断流,上游subtask的watermark不更新则下游的watermark都不更新(subtask断流超时);迟到数据处理:因为watermark表示当前事件发生的真实时间,那晚于watermark的数据到来时,系统会认为数据是迟到的数据。 image.png
  1. Window窗口
  • 基础 分类:Tumble Window(滚动窗口)、Sliding Window(滑动窗口)、Session Window(会话窗口)、全局窗口、Count Window、累计窗口...等等

  • 使用 SQL API:

SELECT
    user,
    TUMBLE_START(order_time, INTERVAL '1' DAY) AS wStart,
    SUM(amount) From Orders
GROUP BY
    TUMBLE(order_time, INTERVAL '1' DAY),
    user

DataStream API:

DataStream<Tuple2<String, Integer> dataStream = env
    .socketTextStream("loclhost", 9999)
    .flatMap(new Solitter())
    .keyBy(value -> value.f0)
    .window(TumblingProcessingTimeWindows.of(Time.second(5)))
    .sum(1);
  • 滚动窗口 窗口划分:每个key单独划分,每条数据只会属于一个窗口 窗口触发:Window结束事件到达的时候一次性触发 image.png

  • 滑动窗口 窗口划分:每个key单独划分,每条数据可能属于多个窗口 窗口触发:Window结束时间到达时候一次性触发

image.png

  • 会话窗口 窗口划分:每个key单独划分,每条数据会单独划分为一个窗口,如果window之间有交集则会对窗口进行merge image.png

  • 迟到数据处理

定义:一条数据到来后,会用WindowAssigner给它划分一个window,一般时间窗口就是一个时间区间如[10:00 11:00),如果划分出来的window end 比当前的watermark值还小,说明窗口已经计算,这条数据认为是迟到数据。只有事件事件会有迟到数据,迟到数据默认处理是丢弃。

Allow lateness:设置一个允许迟到的事件,设置以后窗口正常计算结束后,不会马上清除状态,而是多保留allowlateness时间,如果在这段时间内有数据到来,则继续之前的状态进行计算。适用于:DataStream、SQL

SideOutput(侧输出流):对迟到的数据打一个标签(tag),然后在DataStream上根据tag获取到迟到数据流,然后业务层面自行选择处理。适用于:DataStream

  • 增量VS全量计算 增强计算:每条数据到来直接进行计算,window只存储计算结果sum,reudce,aggregate都是增量计算。SQL中的聚合只有增量计算

全量计算:每条数据到来会存储在window的state中。等到window触发计算的时候,将所有数据拿出来一起计算。process函数就是全量计算

  • EMIT触发 EMIT输出指的是,在window没有结束的时候,提前把window计算的部分结果输出出来。实现方式:在DataStream里面可以通过自定义Trigger来实现,CONTINUE,FIRE(触发计算,但是不清除),PURGE,FIRE_AND_PURGE
  1. 窗口高级优化
  • Mini Batch优化

image.png

image.png

  • 倾斜优化- local-gobal 类似于预聚合 image.png
  • DISTINCT计算状态复用 image.png image.png
  • Panel优化 image.png

image.png