flink (一)

1.概述

Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams.
Apache Flink 是一个框架和分布式处理引擎,用于对无界和有界数据流进行状态计算。
• 流数据更真实地反映了我们的生活方式
• 传统的数据架构是基于有限数据集的
• flink 的优点:
➢ 低延迟
➢ 高吞吐
➢ 结果的准确性和良好的容错性

1.3 应用领域

• 电商和市场营销
➢ 数据报表、广告投放、业务流程需要
• 物联网(IOT)
➢ 传感器实时数据采集和显示、实时报警,交通运输业
• 电信业
➢ 基站流量调配
• 银行和金融业
➢ 实时结算和通知推送,实时检测异常行为

1.4.1 事件驱动(event-driven)

1.4.2 基于流的世界观

在 Flink 的世界观中,一切都是由流组成的. 以流为世界观的架构,获得的最大好处就是具有极低的延迟。
离线数据是有界的流, 即有界流, 在flink-api中为 DataSet;
实时数据是一个没有界限的流, 即无界流. 在flink-api中为 DataStream.

无界数据流: 
无界数据流有一个开始但是没有结束,它们不会在生成时终止并提供数据,必须连续处理无界流,也就是说必须在获取后立即处理 event。
对于无界数据流我们无法等待所有数据都到达,因为输入是无界的,并且在任何时间点都不会完成。
处理无界数据通常要求以特定顺序(例如事件发生的顺序)获取 event,以便能够推断结果完整性。

有界数据流: 
有界数据流有明确定义的开始和结束,可以在执行任何计算之前通过获取所有数据来处理有界流,
处理有界流不需要有序获取,因为可以始终对有界数据集进行排序,有界流的处理也称为批处理。

1.4.3 API分层

Flink 几大模块
⚫ Flink Table & SQL(还没开发完)
⚫ Flink Gelly(图计算)
⚫ Flink CEP(复杂事件处理)

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

最底层级的抽象仅仅提供了有状态流,它将通过过程函数(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 是以表为中心的声明式编程,其中表可能会动态变化(在表达流数据时)。Table API 遵循(扩展的)关系模型:
表有二维数据结构(schema)(类似于关系数据库中的表),同时 API 提供可比较的操作,例如 select、project、join、group-by、aggregate 等。 
Table API 程序声明式地定义了什么逻辑操作应该执行,而不是准确地确定这些操作代码的看上去如何。

尽管 Table API 可以通过多种类型的用户自定义函数(UDF)进行扩展,其仍不如核心 API 更具表达能力,但是使用起来却更加简洁(代码量更少)。
除此之外,Table API 程序在执行之前会经过内置优化器进行优化。

你可以在表与 DataStream/DataSet 之间无缝切换,以允许程序将 Table API 与DataStream 以及 DataSet 混合使用。

Flink 提供的最高层级的抽象是 SQL 。这一层抽象在语法与表达能力上与Table API 类似,但是是以 SQL 查询表达式的形式表现程序。 
SQL 抽象与 Table API交互密切,同时 SQL 查询可以直接在 Table API 定义的表上执行。

目前 Flink 作为批处理还不是主流,不如 Spark 成熟,所以 DataSet 使用的并不是很多。 Flink Table API 和 Flink SQL 也并不完善,大多都由各大厂商自己定制。
实际上 Flink 作为最接近 Google DataFlow 模型的实现,是流批统一的观点,所以基本上使用 DataStream 就可以了。

1.4.4 其他特点

• 支持事件时间(event-time)和处理时间(processing-time)
语义
• 精确一次(exactly-once)的状态一致性保证
• 低延迟,每秒处理数百万个事件,毫秒级延迟
• 与众多常用存储系统的连接
• 高可用,动态扩展,实现7*24小时全天候运行
• 数据模型
– spark 采用 RDD 模型,spark streaming 的 DStream 实际上也就是一组 组小批
数据 RDD 的集合
– flink 基本数据模型是数据流,以及事件(Event)序列
• 运行时架构
– spark 是批计算,将 DAG 划分为不同的 stage,一个完成后才可以计算下一个
– flink 是标准的流执行模式,一个事件在一个节点处理完后可以直接发往下一个节
点进行处理

1.6 数据处理架构的演变

2.flink部署

2.1 standalone模式

单机模式, 直接下载安装即可.

2.2 yarn 模式

2.2.1 session-cluster 模式

#共享资源;适合规模小执行时间短的作业。
Session-Cluster模式需要先启动集群,然后再提交作业,接着会向yarn申请一块空间后,资源永远保持不变。
如果资源满了,下一个作业就无法提交,只能等到yarn中的其中一个作业执行完成后,释放了资源,下个作业才会正常提交。
所有作业共享Dispatcher和 ResourceManager。

在yarn中初始化一个flink 集群,开辟指定的资源,以后提交任务都向这里提交。这个flink集群会常驻在 yarn集群中,除非手工停止。

2.2.2 per-job-cluster 模式

#适合规模大长时间运行的作业。

一个Job会对应一个集群,每提交一个作业会根据自身的情况﹐都会单独向yarn申请资源,直到作业执行完成,
一个作业的失败与否并不会影响下一个作业的正常提交和运行。
独享Dispatcher和 ResourceManager,按需接受资源申请。

每次提交都会创建一个新的flink集群,任务之间互相独立,互不影响,方便管理。任务执行完成之后创建的集群也会消失。

2.3 k8s 模式

1) 概述
在k8s上构建Flink Session Cluster,需要将F1ink集群的组件对应的docker镜像分别在k8s上启动,包括JobManager、TaskManager、JobManagerService 三个镜像服务。每个镜像服务都可以从中央镜像仓库中获取。

2) Flink Session Cluster (具体yaml可以自行搜索配置即可)
// 启动jobmanager-service服务
kubectl qreate -f jobmanager-service.yaml
// 启动jobmanager-depLoyment服务
kubectl create -f jobmanager-deployment.yaml
// 启动taskmanager-depLoyment服务
kubectl create -f taskmanager-deployment.yaml

3)访问Flink UI 页面
集群启动后,就可以通过JobManagerServicers中配置的WebUI端口,用浏览器输入以下url来访问Flink UI页面了:
http://{JobManagerHost:Port}/api/v1/namespaces/default/services/flink-jor:ui/proxy

3.flink的运行时组件

3.1 组件

3.1.1 作业管理器(JobManager)

• 控制一个应用程序执行的主进程,也就是说,每个应用程序都会被一个不同的JobManager 所控制执行。

• JobManager 会先接收到要执行的应用程序,这个应用程序会包括:
作业图(JobGraph)、逻辑数据流图(logical dataflow graph)和打包了所有的类、库和其它资源的JAR包。

• JobManager 会把JobGraph转换成一个物理层面的数据流图,这个图被叫做“执行图”(ExecutionGraph),包含了所有可以并发执行的任务。

• JobManager 会向资源管理器(ResourceManager)请求执行任务必要的资源,也就是任务管理器(TaskManager)上的插槽(slot)。
一旦它获取到了足够的资源,就会将执行图分发到真正运行它们的TaskManager上。
而在运行过程中,JobManager会负责所有需要中央协调的操作,比如说检查点(checkpoints)的协调

3.1.2 任务管理器(TaskManager)

• Flink中的工作进程。通常在Flink中会有多个TaskManager运行,每一个TaskManager都包含了一定数量的插槽(slots)。
插槽的数量限制了TaskManager能够执行的任务数量。

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

• 在执行过程中,一个TaskManager可以跟其它运行同一应用程序的TaskManager交换数据。

• Flink 中每一个TaskManager都是一个JVM进程,它可能会在独立的线程上执行一个或多个子任务。

• 为了控制一个TaskManager能接收多少个task,TaskManager通过task slot来进行控制(一个TaskManager至少有一个slot)。

• 一个 slot 就占用一个线程及相关内存资源。

• 默认情况下,Flink 允许子任务共享 slot,即使它们是不同任务的子任务。 这样的结果是,一个 slot 可以保存作业的整个管道。

• Task Slot 是静态的概念,是指 TaskManager 具有的并发执行能力

3.1.3 资源管理器(ResourceManager)

• 主要负责管理任务管理器(TaskManager)的插槽(slot),TaskManger 插槽是Flink中定义的处理资源单元。

• Flink为不同的环境和资源管理工具提供了不同资源管理器,比如YARN、Mesos、 K8s,以及standalone部署。

• 当JobManager申请插槽资源时,ResourceManager会将有空闲插槽的TaskManager分配给JobManager。
如果ResourceManager没有足够的插槽来满足JobManager的请求,它还可以向资源提供平台发起会话,以提供启动TaskManager进程的容器。

3.1.4 分发器(Dispatcher)

• 可以跨作业运行,它为应用提交提供了REST接口。

• 当一个应用被提交执行时,分发器就会启动并将应用移交给一个JobManager。

• Dispatcher也会启动一个Web UI,用来方便地展示和监控作业执行的信息。

• Dispatcher在架构中可能并不是必需的,这取决于应用提交运行的方式。

3.2 flink程序的组成

3.2.1 程序与数据流(DataFlow)

• 所有的Flink程序都是由三部分组成的: Source 、 Transformation 和 Sink。
• Source 负责读取数据源,Transformation 利用各种算子进行处理加工,Sink负责输出。
• 基本流程: environment --> source --> transformation --> sink

4.window(窗口相关概念)

4.1 概述

streaming 流式计算是一种被设计用于处理无限数据集的数据处理引擎,
而无限数据集是指一种不断增长的本质上无限的数据集,
而 window 是一种切割无限数据为有限块进行处理的手段。

Window 是无限数据流处理的核心, Window 将一个无限的 stream 拆分成有限大小的” buckets”桶,我们可以在这些桶上做计算操作。

4.2 分类

Window 可以分成两类:
➢ CountWindow:按照指定的数据条数生成一个 Window,与时间无关。包括: 滚动窗口, 滑动窗口。
➢ TimeWindow:按照时间生成 Window。包括: 滚动窗口, 滑动窗口, 会话窗口。

以 TimeWindow为例, 可以分为如下几种类型:

4.2.1 滚动窗口(Tumbling Windows)

将数据依据固定的窗口长度对数据进行切片。
特点:时间对齐,窗口长度固定,没有重叠。
滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠。

适用场景:适合做 BI 统计等(做每个时间段的聚合计算)。

例如:如果你指定了一个 5 分钟大小的滚动窗口,窗口的创建如下图所示:

4.2.2 滑动窗口(Sliding Windows)

滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。
特点:时间对齐,窗口长度固定, 可以有重叠。
滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,窗口的大小由窗口大小参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。
因此,滑动窗口如果滑动参数小于窗口大小的话,窗口是可以重叠的,在这种情况下元素会被分配到多个窗口中。

适用场景:对最近一个时间段内的统计(求某接口最近 5min 的失败率来决定是否要报警)。

例如,你有 10 分钟的窗口和 5 分钟的滑动,那么每个窗口中 5 分钟的窗口里包含着上个 10 分钟产生的数据,
如下图所示:

4.2.3 会话窗口(Session Windows)

由一系列事件组合一个指定时间长度的 timeout 间隙组成,类似于 web 应用的session,也就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间无对齐。
session 窗口分配器通过 session 活动来对元素进行分组, session 窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,
相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。
一个 session 窗口通过一个 session 间隔来配置,这个 session 间隔定义了非活跃周期的长度,
当这个非活跃周期产生,那么当前的 session 将关闭并且后续的元素将被分配到新的 session 窗口中去。

4.3 相关API

窗口函数(window function)
• 窗口分配器 —— window() 方法
➢ 我们可以用 .window() 来定义一个窗口,然后基于这个 window 去做一些聚
合或者其它处理操作。注意 window () 方法必须在 keyBy 之后才能用(windowAll例外)。
➢ Flink 提供了更加简单的 .timeWindow 和 .countWindow 方法,用于定义
时间窗口和计数窗口。

• window function 定义了要对窗口中收集的数据做的计算操作, 可以分为两类
➢ 增量聚合函数(incremental aggregation functions)
• 每条数据到来就进行计算,保持一个简单的状态
• ReduceFunction, AggregateFunction
➢ 全窗口函数(full window functions)
• 先把窗口所有数据收集起来,等到计算的时候会遍历所有数据
• ProcessWindowFunction,WindowFunction

其它可选 API
• .trigger() —— 触发器: 定义 window 什么时候关闭,触发计算并输出结果
• .evictor() —— 移除器: 定义移除某些数据的逻辑
• .allowedLateness() —— 允许处理迟到的数据
• .sideOutputLateData() —— 将迟到的数据放入侧输出流
• .getSideOutput() —— 获取侧输出流

5.时间语义与watermark (解决乱序数据的问题)

5.1 时间语义及分类

Event Time:
是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间, Flink 通过时间戳分配器访问事件时间戳。

Ingestion Time:
是数据进入 Flink 的时间。

Processing Time:
是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是 Processing Time。

#eventTime 十分重要
在 Flink 的流式处理中,绝大部分的业务都会使用 eventTime,一般只在 eventTime 无法使用时,才会被迫使用 ProcessingTime 或者 IngestionTime。
如果要使用 EventTime,那么需要引入 EventTime 的时间属性,引入方式如下所示:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment
// 从调用时刻开始给 env 创建的每一个 stream 追加时间特征
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

5.2 watermark

我们知道,流处理从事件产生,到流经 source,再到 operator,中间是有一个过程和时间的,
虽然大部分情况下,流到 operator 的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络、分布式等原因,导致乱序的产生,
所谓乱序,就是指 Flink 接收到的事件的先后顺序不是严格按照事件的 Event Time 顺序排列的。

那么此时出现一个问题,一旦出现乱序,如果只根据 eventTime 决定 window 的运行,我们不能明确数据是否全部到位,但又不能无限期的等下去,
此时必须要有个机制来保证一个特定的时间后,必须触发 window 去进行计算了,这个特别的机制,就是 Watermark。

⚫ Watermark 是一种衡量 Event Time 进展的机制。
⚫ Watermark 是用于处理乱序事件的,而正确的处理乱序事件,通常用Watermark 机制结合 window 来实现。
⚫ 数据流中的 Watermark 用于表示 timestamp 小于 Watermark 的数据,都已经到达了,因此, window 的执行也是由 Watermark 触发的。
⚫ Watermark 可以理解成一个延迟触发机制,我们可以设置 Watermark 的延时时长 t,每次系统会校验已经到达的数据中最大的 maxEventTime,
然后认定 eventTime小于 maxEventTime - t 的所有数据都已经到达,如果有窗口的停止时间等于maxEventTime – t,那么这个窗口被触发执行。
⚫ watermark 用来让程序自己平衡延迟和结果正确性
⚫ watermark 是一条特殊的数据记录
⚫ watermark 必须单调递增,以确保任务的事件时间时钟在向前推进,而
不是在后退
⚫ watermark 与数据的时间戳相关

当 Flink 接收到数据时, 会按照一定的规则去生成 Watermark,这条 Watermark 就等于当前所有到达数据中的 maxEventTime - 延迟时长,也就是说, 
Watermark 是基于数据携带的时间戳生成的,一旦 Watermark 比当前未触发的窗口的停止时间要晚,那么就会触发相应窗口的执行。
由于 event time 是由数据携带的,因此,如果运行过程中无法获取新的数据,那么没有被触发的窗口将永远都不被触发。

上图中,我们设置的允许最大延迟到达时间为 2s,所以时间戳为 7s 的事件对应的 Watermark 是 5s,时间戳为 12s 的事件的 Watermark 是 10s,
如果我们的窗口 1是 1s~5s,窗口 2 是 6s~10s,那么时间戳为 7s 的事件到达时的 Watermarker 恰好触发窗口 1,
时间戳为 12s 的事件到达时的 Watermark 恰好触发窗口 2。

Watermark 就是触发前一窗口的“关窗时间”,一旦触发关门那么以当前时刻为准在窗口范围内的所有所有数据都会收入窗中。
只要没有达到水位那么不管现实中的时间推进了多久都不会触发关窗。

5.3 watermark 的引入

5.3.1 对于无序数据

• Event Time 的使用一定要指定数据源中的时间戳
• 调用 assignTimestampAndWatermarks 方法,传入一个
BoundedOutOfOrdernessTimestampExtractor,就可以指定

dataStream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<StuEntity>(Time.seconds(2L)) {
    @Override
    public long extractTimestamp(StuEntity element) {
          return element.getTimestamp() * 1000L;
    }
});

5.3.2 对于排好序的数据

• 对于排好序的数据,不需要延迟触发,可以只指定时间戳就行了

dataStream.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<StuEntity>(Time.seconds(2L)) {
    @Override
    public long extractTimestamp(StuEntity element) {
          return element.getTimestamp() * 1000L;
    }
});

5.3.3 实现 TimestampAssigner 接口自定义扩展

Flink 暴露了 TimestampAssigner 接口供我们实现,使我们可以自定义
如何从事件数据中抽取时间戳和生成watermark
该接口定义了抽取时间戳,以及生成 watermark 的方法,有两种类型
➢ AssignerWithPeriodicWatermarks
• 周期性的生成 watermark:系统会周期性的将 watermark 插入到流中
• 默认周期是200毫秒,可以使用
ExecutionConfig.setAutoWatermarkInterval() 方法进行设置
• 升序和前面乱序的处理 BoundedOutOfOrdernessTimestampExtractor,
都是基于周期性 watermark 的。
➢ AssignerWithPunctuatedWatermarks
• 没有时间周期规律,可打断的生成 watermark

5.3.4 watermark 的设定

• 在 Flink 中,watermark 由应用程序开发人员生成,这通常需要对相应
的领域有一定的了解
• 如果watermark设置的延迟太久,收到结果的速度可能就会很慢,解决
办法是在水位线到达之前输出一个近似结果
• 而如果watermark到达得太早,则可能收到错误结果,不过 Flink 处理迟
到数据的机制可以解决这个问题

6.flink中的状态管理

6.1 基本概念

• 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状
态
• 可以认为状态就是一个本地变量,可以被任务的业务逻辑访问
• Flink 会进行状态管理,包括状态一致性、故障处理以及高效存储和访问,以
便开发人员可以专注于应用程序的逻辑

6.2 分类

• 在 Flink 中,状态始终与特定算子相关联
• 为了使运行时的 Flink 了解算子的状态,算子需要预先注册其状态
➢ 总的说来,有两种类型的状态:
• 算子状态(Operator State)
• 算子状态的作用范围限定为算子任务
• 键控状态(Keyed State)
• 根据输入数据流中定义的键(key)来维护和访问

6.2.1 算子状态(Operator State)

#概念
• 算子状态的作用范围限定为算子任务,由同一并行任务所处理的所有数据都
可以访问到相同的状态
• 状态对于同一子任务而言是共享的
• 算子状态不能由相同或不同算子的另一个子任务访问

#算子状态数据结构
➢ 列表状态(List state)
• 将状态表示为一组数据的列表
➢ 联合列表状态(Union list state)
• 也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故
障时,或者从保存点(savepoint)启动应用程序时如何恢复
➢ 广播状态(Broadcast state)
• 如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特
殊情况最适合应用广播状态。

6.2.2 键控状态(Keyed State)

#概念
• 需要先做 keyBy()
• 键控状态是根据输入数据流中定义的键(key)来维护和访问的
• Flink 为每个 key 维护一个状态实例,并将具有相同键的所有数据,都分区到
同一个算子任务中,这个任务会维护和处理这个 key 对应的状态
• 当任务处理一条数据时,它会自动将状态的访问范围限定为当前数据的 key

#键控状态数据结构
➢ 值状态(Value state)
• 将状态表示为单个的值
➢ 列表状态(List state)
• 将状态表示为一组数据的列表
➢ 映射状态(Map state)
• 将状态表示为一组 Key-Value 对
➢ 聚合状态(Reducing state & Aggregating State)
• 将状态表示为一个用于聚合操作的列表

6.3 状态后端(State Backends)

• 每传入一条数据,有状态的算子任务都会读取和更新状态
• 由于有效的状态访问对于处理数据的低延迟至关重要,因此每个并行任务都会在本地维护其状态,以确保快速的状态访问
• 状态的存储、访问以及维护,由一个可插入的组件决定,这个组件就叫做状态后端(state backend)
• 状态后端主要负责两件事:本地的状态管理,以及将检查点(checkpoint)状态写入远程存储

6.3.1 状态后端实现类

➢ MemoryStateBackend (生产不用)
• 内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储在
TaskManager 的 JVM 堆上,而将 checkpoint 存储在 JobManager 的内存
中
• 特点:快速、低延迟,但不稳定

➢ FsStateBackend (容易OOM)
• 将 checkpoint 存到远程的持久化文件系统(FileSystem)上,而对于本地状
态,跟 MemoryStateBackend 一样,也会存在 TaskManager 的 JVM 堆上
• 同时拥有内存级的本地访问速度,和更好的容错保证

➢ RocksDBStateBackend
• 将所有状态序列化后,存入本地的 RocksDB 中存储。

7.容错机制(checkpoint)

7.1 一致性检查点(Checkpoints)

• Flink 故障恢复机制的核心,就是应用状态的一致性检查点
• 有状态流应用的一致检查点,其实就是所有任务的状态,在某个时间点的一份
拷贝(一份快照);这个时间点,应该是所有任务都恰好处理完一个相同的输
入数据的时候

7.2 从检查点恢复状态流程

• 在执行流应用程序期间,Flink 会定期保存状态的一致检查点
• 如果发生故障, Flink 将会使用最近的检查点来一致恢复应用程序的状态,并
重新启动处理流程

遇到故障之后:
• 第一步就是重启应用
• 第二步是从 checkpoint 中读取状态,将状态重置: 从检查点重新启动应用程序后,其内部状态与检查点完成时的状态完全相同
• 第三步:开始消费并处理检查点到发生故障之间的所有数据
这种检查点的保存和恢复机制可以为应用程序状态提供“精确一次”
(exactly-once)的一致性,因为所有算子都会保存检查点并恢复其所有状
态,这样一来所有的输入流就都会被重置到检查点完成时的位置

image

image

image

image

7.3 保存点(Savepoints)

• Flink 还提供了可以自定义的镜像保存功能,就是保存点(savepoints)
• 原则上,创建保存点使用的算法与检查点完全相同,因此保存点可以认
为就是具有一些额外元数据的检查点
• Flink不会自动创建保存点,因此用户(或者外部调度程序)必须明确地
触发创建操作
• 保存点是一个强大的功能。 除了故障恢复外,保存点可以用于:有计划
的手动备份,更新应用程序,版本迁移,暂停和重启应用,等等

8.状态一致性

8.1 什么是状态一致性

• 有状态的流处理,内部每个算子任务都可以有自己的状态
• 对于流处理器内部来说,所谓的状态一致性,其实就是我们所说的计
算结果要保证准确。
• 一条数据不应该丢失,也不应该重复计算
• 在遇到故障时可以恢复状态,恢复以后的重新计算,结果应该也是完
全正确的。

8.2 状态一致性分类

• AT-MOST-ONCE(最多一次)
➢ 当任务故障时,最简单的做法是什么都不干,既不恢复丢失的状态,也不重
播丢失的数据。 At-most-once 语义的含义是最多处理一次事件。
• AT-LEAST-ONCE(至少一次)
➢ 在大多数的真实应用场景,我们希望不丢失事件。这种类型的保障称为 atleast-once,意思是所有的事件都得到了处理,而一些事件还可能被处理多
次。
• EXACTLY-ONCE(精确一次)
➢ 恰好处理一次是最严格的保证,也是最难实现的。恰好处理一次语义不仅仅
意味着没有事件丢失,还意味着针对每一个数据,内部状态仅仅更新一次。

8.3 一致性检查点(Checkpoints)

• Flink 使用了一种轻量级快照机制 —— 检查点(checkpoint)来保
证 exactly-once 语义
• 有状态流应用的一致检查点,其实就是:所有任务的状态,在某个时
间点的一份拷贝(一份快照)。 而这个时间点,应该是所有任务都恰
好处理完一个相同的输入数据的时候。
• 应用状态的一致检查点,是 Flink 故障恢复机制的核心

8.4 端到端(end-to-end)状态一致性

• 目前我们看到的一致性保证都是由流处理器实现的,也就是说都是在
Flink 流处理器内部保证的;而在真实应用中,流处理应用除了流处
理器以外还包含了数据源(例如 Kafka)和输出到持久化系统
• 端到端的一致性保证,意味着结果的正确性贯穿了整个流处理应用的
始终;每一个组件都保证了它自己的一致性
• 整个端到端的一致性级别取决于所有组件中一致性最弱的组件

8.5 端到端 exactly-once

• flink内部保证 —— checkpoint
• source 端 —— 可重设数据的读取位置 (比如kafka)
• sink 端 —— 从故障恢复时,数据不会重复写入外部系统
	➢ 幂等写入
	➢ 事务写入

8.5.1 幂等写入(Idempotent Writes)

所谓幂等操作,是说一个操作,可以重复执行很多次,但只导致一次
结果更改,也就是说,后面再重复执行就不起作用了

8.5.2 事务写入(Transactional Writes)

• 事务(Transaction)
➢ 应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所
作的所有更改都会被撤消
➢ 具有原子性:一个事务中的一系列的操作要么全部成功,要么一个都不做

• 实现思想:构建的事务对应着 checkpoint,等到 checkpoint 真正完成
的时候,才把所有对应的结果写入 sink 系统中

• 实现方式
➢ 预写日志
➢ 两阶段提交

8.5.2.1 预写日志(Write-Ahead-Log,WAL)

• 把结果数据先当成状态保存,然后在收到 checkpoint 完成的通知时,
一次性写入 sink 系统
• 简单易于实现,由于数据提前在状态后端中做了缓存,所以无论什么
sink 系统,都能用这种方式一批搞定
• DataStream API 提供了一个模板类:GenericWriteAheadSink,来
实现这种事务性 sink

8.5.2.2 两阶段提交(Two-Phase-Commit,2PC)

• 对于每个 checkpoint,sink 任务会启动一个事务,并将接下来所有
接收的数据添加到事务里
• 然后将这些数据写入外部 sink 系统,但不提交它们 —— 这时只是
“预提交”
• 当它收到 checkpoint 完成的通知时,它才正式提交事务,实现结果
的真正写入
➢ 这种方式真正实现了 exactly-once,它需要一个提供事务支持的外部
sink 系统。 Flink 提供了 TwoPhaseCommitSinkFunction 接口。

2PC 对外部 sink 系统的要求

• 外部 sink 系统必须提供事务支持,或者 sink 任务必须能够模拟外部系
统上的事务
• 在 checkpoint 的间隔期间里,必须能够开启一个事务并接受数据写入
• 在收到 checkpoint 完成的通知之前,事务必须是“等待提交”的状态。
在故障恢复的情况下,这可能需要一些时间。如果这个时候sink系统关
闭事务(例如超时了),那么未提交的数据就会丢失
• sink 任务必须能够在进程失败后恢复事务
• 提交事务必须是幂等操作

不同 Source 和 Sink 的一致性保证

整个端到端的一致性级别取决于所有组件中一致性最弱的组件

image

8.6 Flink+Kafka 端到端状态一致性的保证

• flink内部 —— 利用 checkpoint 机制,把状态存盘,发生故障的时候可以恢
复,保证内部的状态一致性
• source —— kafka consumer 作为 source,可以将偏移量保存下来,
如果后续任务出现了故障,恢复的时候可以由连接器重置偏移量,重新
消费数据,保证一致性
• sink —— kafka producer 作为sink,采用两阶段提交 sink,需要实现
一个 TwoPhaseCommitSinkFunction

8.7 Exactly-once 两阶段提交步骤

• 第一条数据来了之后,开启一个 kafka 的事务(transaction),正常写入 kafka 分区日志但标记为未提交,这就是“预提交”

• jobmanager 触发 checkpoint 操作,barrier 从 source 开始向下传递,遇到barrier 的算子将状态存入状态后端,并通知 jobmanager

• sink 连接器收到 barrier,保存当前状态,存入 checkpoint,通知 jobmanager,并开启下一阶段的事务,用于提交下个检查点的数据

• jobmanager 收到所有任务的通知,发出确认信息,表示 checkpoint 完成

• sink 任务收到 jobmanager 的确认信息,正式提交这段时间的数据

• 外部kafka关闭事务,提交的数据可以正常消费了。

image

参考资源
https://gitee.com/zy_bear/flink-research

posted @ 2021-08-07 10:49  psy_code  阅读(184)  评论(0编辑  收藏  举报