基于apacheflink的流处理前三章分享第1章状态

第1章 状态化流处理概述

传统数据处理

绝大多数企业所实现的传统架构都会将数据处理分为两类:

  • 事务型处理
  • 分析型处理

事务型处理

企业在日常业务运营过程中会用到各类应用,例如:客户管理管理软件、基于Web的应用等,这些应用系统通常都会设置独立的数据处理层(应用程序本身)和数据存储层(事务型数据库系统)。
这些应用通常会连接外部服务或实际用户,并持续处理诸如订单、邮件、网站点击等传入的数据。期间每处理一条事件,应用都会通过执行远程数据库系统的事务来读取或者更新状态,多个应用会共享同一个数据库系统,有时候还会访问相同的数据库或表。
在这里插入图片描述

分析型处理

存储于不同事务型数据库系统中的数据,可以为企业提供业务运营相关的分析见解。然而用于存储事务性数据的多个数据库系统通常都是相互隔离的,对于分析类查询,我们通常不会直接在事务型数据库上进行,而是将数据复制到一个撰文用来处理分析类查询的数据仓库为了填充数据仓库,需要将事务型数据库系统中数据拷贝过去。这个向数据仓库拷贝数据的过程被称为提取-转换-加载(Extract-Transform-Load,ETL)。
ETL的基本流程是:

  1. 从事务型数据库中提取数据
  2. 将其转换为通用表示形式(可能包含数据验证、数据归一化、编码、去重、表模式转换等工作)
  3. 加载到数据分析数据库中
    为了保持数据仓库中的数据同步,ETL过程需要周期性的执行

    1. 在这里插入图片描述

状态化流处理

几乎所有的数据都是以连续事件流的形式产生的。事实上,现实世界中很难找到那种瞬间就生成完整数据集的例子。
任何一个处理事件流的应用,如果要支持跨多条记录的转换操作,都必须是有状态的,即能够存储和访问中间结果。应用收到事件后可以执行包括读写状态在内的任意计算。原则上,需要在应用中访问的状态有多种可选的存储位置,例如:程序变量、本地文件、嵌入式或外部数据库等。
在这里插入图片描述

有状态的流处理应用通常分为三类:

  1. 事件驱动型应用,通过接受事件流触发特定应用业务逻辑的有状态的流式应用,如实时推荐、异常检测等
  2. 数据管道型应用,以低延迟的方式从不同的外部系统获取、转换并插入数据,并在段时间内处理大批量数据的应用,提供多样化的数据源、数据汇连接器。Flink可以做到上述一切。
  3. 数据分析型应用,主要有周期性的批处理和持续性的流处理两类应用。

Flink快览

Apache Flink是一个集众多具有竞争力的特性于一身的第三代流处理引擎。它支持精确的流处理,能同事满足各种规模下对高吞吐和低延迟的要求,尤其是以下功能使其能在同类系统中脱颖而出:

  • 同时支持事件时间和处理时间语义
  • 提供精确一次的状态一致性保障
  • 在每秒处理数百万条事件的同时保持毫秒级延迟
  • 层次化的API
  • 常见的存储系统的连接器
  • 支持高可用配置
  • 允许在不丢失应用状态的前提下更新作业代码,或进行跨Flink集群的作业迁移
  • 提供详细、可定制的系统及应用指标(metrics)集合,用于提前定位和响应问题
  • Flink同时也是一个成熟的批处理引擎(批是流的特例,即有界流)

第2章 流处理基础

Dataflow编程概览

Dataflow图

Dataflow程序描述了数据如何在不同的操作之间流动。Dataflow程序通常表示为有向图。图中顶点称为算子(逻辑Dataflow图称为算子,物理Dataflow图称为任务),表示计算;而边表示数据依赖关系。算子是Dataflow程序的基本功能单元,他们从输入获取数据,对其进行计算,然后产生数据并发往输出供后续处理。没有输入端的算子成为数据源,没有输出端的算子成为数据汇。一个Dataflow图至少有一个数据源和一个数据汇。

数据并行和任务并行

数据并行:将输入数据分组,让同一操作的多个任务并行执行在不同数据子集上。将计算负载分配到多个节点上从而允许处理大规模的数据
任务并行:让不同算子的任务(基于相同或不通的数据)并行计算,可以更好的利用集群的计算资源

数据交换

数据交换策略定义了如何将数据项分配给物理Dataflow图中的不同任务。常见有如下四种数据交换策略:

  • 转发策略:在发送端任务和接收端任务之间一对一的进行数据传输。如果两端的任务运行在同一物理机器上,可以避免网络通信
  • 广播策略:把每个数据项发往下游算子的全部任务
  • 基于键值的策略:根据魔衣键值属性对数据分区,并保证键值相同的数据项会交由同一任务处理
  • 随机策略:将数据均匀分配至算子的所有任务,以实现计算任务的负载均衡

并行流处理

数据流定义:一个可能无限的事件序列

延迟和吞吐

延迟:处理一个事件所需的时间。本质上,它是从接收事件到在输出中观察到事件处理效果的时间间隔。
吞吐:用来衡量系统处理能力(处理速率)的指标,它告诉我们系统每单位时间可以处理多少事件。如果系统持续以力不能及的高速率接收数据,那么缓冲区可能会用尽,继而导致数据丢失,这种情形同城称为被压。
延迟和吞吐并非相互独立的指标。如果事件在数据处理管道中传输时间太久,我们将难以保证高吞吐;同样,如果系统性能不足,事件很容易堆积缓冲,必须等待一段时间才能处理。

数据流上的操作

流处理引擎通常会提供一系列内置操作来实现数据流的获取、转换,以及输出。这些算子可以组合生成Dataflow处理图,从而时间流式应用所需的逻辑。常见有如下流式操作:

数据接入和数据输出

数据接入和数据输出操作允许流处理引擎和外部系统通信。
数据接入操作是从外部数据源获取原始数据并将其转换成合适后续处理的格式,该类算子称为数据源。
数据输出操作是将数据以合适外部系统使用的格式输出,该类算子称为数据汇。

转换操作

转换操作是一类”只过一次“的操作,它们会分别处理每个事件,对其应用某些转换并产生一条心的输出流。

滚动聚合

滚动聚合(如求和、求最值)会根据每个到来的事件持续更新结果。聚合操作都是有状态的,它们通过将新到来的事件合并到已有状态来生成更新后的聚合值。

窗口操作

有些操作必须收集并缓冲记录才能计算结果,例如流式join或像是求中位数的整体聚合。为了在无限数据流上高效的执行这些操作,必须对操作的数据加以限制。窗口操作会持续创建一些称为“桶”的有限事件合集,并允许我们基于这些有限集进行计算。
常见有如下几种窗口类型:
滚动窗口:将事件分配到长度固定且互不重叠的桶中。在窗口边界通过后,所有事件会发送给计算函数处理。可分为基于数量的滚动窗口和基于时间的滚动窗口。
滑动窗口:将事件分配到大小固定且允许重叠的桶中,这意味着每个事件可能会同时属于多个桶。我们指定长度和滑动间隔连定义滑动窗口。
会话窗口:将属于同一会话的事件分配到相同桶中。会话窗口根据会话间隔将事件分为不同的会话,该间隔值定义了会话在关闭前的非活动事件长度。

时间语义

处理时间

处理时间是当前流处理算子所在机器上的本地时钟时间。

时间事件

事件时间是数据流中事件实际发生的时间,它以附加在数据流中事件的时间戳为依据。这些时间戳通常在事件数据进入流处理管道之前就存在。
事件时间将处理速度和结果内容彻底解耦。基于事件时间的操作是可预测的,其结果具有确定性。无论数据流的处理速度如何、事件到达算子的顺序怎样,基于事件时间的窗口都会生成同样的结果。
使用事件时间要克服的挑战之一是如何处理延迟事件。普遍存在的无序问题也可以借此解决。

水位线

水位线是一个全局进度指标,表示我们确信不会再有延迟事件到来的某个时间点。本质上,水位线提供了一个逻辑时钟,用来通知系统当前的事件时间。当一个算子收到事件为T的水位线,就可以认为不会再收到任何时间戳小于或等于T的事件了。

状态和一致性模型

状态在数据处理中无处不在,任何一个稍复杂的计算都要用它。不难想象,支持有状态算子将面临很多实现上的挑战:

  1. 状态管理:系统需要高效的管理状态并保证它们不受并发更新影响
  2. 状态划分:把状态按照键值划分,并独立管理每一部分
  3. 状态恢复:最后一个也是最大的挑战在于,有状态算子需要保证状态可以恢复,并且即使出现故障也要确保结果正确。

结果保障

结果保障指的是流处理引擎内部状态的一致性。结果保障可分为如下几种:

  • 至多一次:保证每个事件至多被处理一次,在故障时既不恢复丢失的状态,也不重放丢失的事件
  • 至少一次:所有的事件都会被处理,但有些可能会被处理多次。为了确保至少一次语义,需要从源头或者缓冲区中重放事件。
  • 精确一次:既不丢失事件,也不重复处理事件。

第3章 Apache Flink架构

系统架构

Flink是一个用于状态化并行流处理的分布式系统。Flink在已有集群基础设施和服务至上专注于它的核心功能——分布式数据流处理。Flink和很多集群管理器(如Apache Mesos、YARN及Kubernets)都能很好的集成;同时它也可以通过配置,作为独立的集群来运行。Flink没有提供分布式持久化存储,而是利用了现有的分布式文件系统(如HDFS)或对象存储(如S3)。它依赖Apache Zookeeper来完成高可用性设置中的领导选举。

搭建Flink所需组件

  • JobManager:作为主进程,JobManager控制着单个应用程序的执行。换句话说,每个应用都由一个不同的JobManager掌控。JobManager接收需要执行的应用,该应用会包含一个所谓的JobGraph,JobManager将其转化为ExecutionGraph,然后从ResourceManager申请执行任务的必要资源(处理槽),然后在将ExecutionGraph中的任务分发给TaskManager来执行。在执行的过程中JobManager还要负责所有需要集中协调的操作,如创建检查点。
  • ResourceManager:负责管理Flink的处理资源单元——TaskManager处理槽。当JobManager申请TaskManager处理槽时,ResourceManager会指示一个拥有空闲处理槽的TaskManager将其处理槽提供给JobManager。如果处理槽数无法满足JobManager的请求,ResourceManager可以和资源提供者通信,让它们提供额外容器来启动更多的TaskManager进程。同时,ResourceManager还负责终止空闲的TaskManager以释放计算资源。
  • TaskManager:工作进程。通常在Flink搭建过程中会启动多个TaskManager,每个TaskManager提供一定数量的处理槽,处理槽的数目限制了一个TaskManager可执行的任务数。
  • Dispatcher:跨多个作业运行。提供一个REST接口来让我们提交需要执行的应用。一旦某个应用提交执行,Dispatcher会启动一个JobManager并将应用转交给它。Dispatcher同时还会启动一个WebUI,用来提供有关作业执行的信息。
    在这里插入图片描述

高可用设置

TaskManager故障

如果部分TaskManager故障,JobManager会向ResourceManager申请相应数量的处理槽。如果无法完成,JobManager将无法重启应用,直到有足够数量的可用处理槽。

JobManager故障

JobManager用于控制流式应用执行以及保存该过程中的源数据,如果JobManager进程消失,流式应用将无法继续处理数据。这就导致JobManager成为Flink应用中的一个单点失效组件。
JobManager在高可用模式下工作时,会依赖Zookeeper完成作业管理及元数据的迁移。具体步骤如下:

  1. JobManager将JobGraph以及全部所需元数据(例如应用的JAR文件)写入一个远程持久化存储系统中
  2. 将存储位置的路径地址写入ZK
  3. JobManager故障时,新进程从ZK获取存储位置,并从远程持久化存储系统中获取相关数据,申请处理槽,重启应用并利用最近一次检查点重置任务状态

Flink中的数据传输

在运行过程中,应用的任务会持续进行数据交换。TaskManager负责将数据从发送任务传输至接收任务。记录并非逐个发送的,而是在缓冲区中以批次形式发送,该技术是有效利用网络资源、实现高吞吐的基础。
发送端和接收端的任务运行在不同的TaskManager进程中时,数据交换需要利用操作系统的网络栈进行通信。在同一个TaskManager进程中时,数据会放在缓冲区和队列中,不涉及网络通信。

基于信用值的流量控制

通过网络连接逐条发送记录不但低效,还会导致很多额外开销。若想充分利用网络连接带宽,就要对数据进行缓冲。在流处理环境下,缓冲的一个明显缺点是会增加延迟,因为记录首先要收集到缓冲区而不会立即发送。Flink实现了一个基于信用值的流量控制机制,它的工作原理如下:接收任务会给发送任务授予一定信用值,其实就是保留一些用来接收它的数据的网络缓冲。一旦发送端收到信用通知,就会在信用值所限定的范围内尽可能多地传输缓冲数据,并会附带上积压量(已经填满准备传输的网络缓冲数目)大小。接收端使用保留的缓冲来处理收到的数据,同时依据各发送端的积压量信息来计算所有相连的发送端在下一轮的信用优先级。

任务链接

任务链接是Flink采用的一种用来降低某些情况下的本地通信开销的优化技术。任务链接的前提条件是,多个算子必须有相同的并行度且通过本地转发通道相连。
在这里插入图片描述在这里插入图片描述

事件时间处理

时间戳

在事件时间模式下,Flink流式应用处理的所有记录都必须包含时间戳。时间戳将记录和特定的时间点关关联,这些时间点通常是记录所对应事件的发生时间。但实际上应用可以自由的选择时间戳的含义,只要保证流记录的时间戳会随着数据流的前进大致递增即可。

水位线

除了记录的时间戳,Flink基于事件时间的应用还必须提供水位线。水位线用于在事件时间应用中推断每个任务当前的事件时间。基于时间的算子会使用这个时间来触发计算并推动进度前进。
水位线本质上是一个包含时间戳信息的特殊记录。
水位线拥有两个基本特征:

  1. 必须单调递增。这是为了确保任务中的事件时间时钟正确前进,不会倒退
  2. 和记录的时间戳存在联系。一个时间戳为T的水位线表示,接下来所有记录的时间戳一定都大于T

状态管理

在Flink中,状态都是和特定的算子相关联。根据作用域的不同,状态可以分为两类:算子状态和键值分区状态。

算子状态

算子状态的作用域是某个算子任务,这意味着所有在同一个并行任务之内的记录都能访问到相同的状态。

键值分区状态

键值分区状态会按照算子输入记录所定义的键值来进行维护或访问。Flink为每个键值都维护了一个状态,该状态总是位于处理对应键值记录的算子任务上。

状态后端

为了保证快速访问状态,每个并行任务都会把状态维护在本地。至于状态具体的存储、访问和维护,则是由一个成为状态后端的可拔插组件来决定。状态后端主要负责两件事:本地状态管理和将状态以检查点的方式写入远程持久化存储中。

检查点、保存点及状态恢复

一致性检查点

Flink的故障恢复机制需要基于应用状态的一致性检查点。有状态的流式应用的一致性检查点是在所有任务处理完灯亮的原始输入后对全部任务状态进行的一个拷贝。
在这里插入图片描述

从一致性检查点中恢复

应用恢复需要经过3个步骤:

  1. 重启整个应用
  2. 利用最新的检查点重置任务状态
  3. 恢复所有任务的运行

Flink检查点算法

  • Flink 的检查点算法用到了一种称为分界线(barrier)的特殊数据形式,用来把一条流上数据按照不同的检查点分开
  • 分界线之前到来的数据导致的状态更改,都会被包含在当前分界线所属的检查点中;而基于分界线之后的数据导致的所有更改,就会被包含在之后的检查点中。

算法操作解析 :

  1. 现在是一个有两个输入流的应用程序,用并行的两个 Source 任务来读取
  2. 两条自然数数据流,蓝色数据流已经输出完蓝3了,黄色数据流输出完黄4了
  3. 在Souce端 Source1 接收到了数据蓝3 正在往下游发向一个数据蓝2 和 蓝3; Source2 接受到了数据黄4,且往下游发送数据黄4
  4. 偶数流已经处理完黄2 所以后面显示为2, 奇数流处理完蓝1 和 黄1 黄3 所以为5 并分别往下游发送每次聚合后的结果给Sink
    在这里插入图片描述
  5. JobManager 会向每个 source 任务发送一条带有新检查点 ID 的消息,通过这种方式来启动检查点,这个带有新检查点ID的东西为barrier,图中三角型表示,2只是ID
    在这里插入图片描述
  6. 在Source端接受到barrier后,将自己此身的3 和 4 的数据,将它们的状态写入检查点,且向JobManager发送checkpoint成功的消息(状态后端在状态存入检查点之后,会返回通知给 source 任务,source 任务就会向 JobManager 确认检查点完),然后向下游分别发出一个检查点 barrier
  7. 可以看出在Source接受barrier时,数据流也在不断的处理,不会进行中断,
  8. 此时的偶数流已经处理完蓝2变成了4,但是还没处理到黄4,只是下游发送了一个次数的数据4,而奇数流已经处理完蓝3变成了8,并向下游发送了8
  9. 此时barrier都还未到奇数流和偶数流
    在这里插入图片描述
  10. 此时蓝色流的barrier先一步抵达了偶数流,黄色的barrier还未到,但是因为数据的不中断一直处理,此时的先到的蓝色的barrier会将此时的偶数流的数据4进行缓存处理,流接着处理接下来的数据等待着黄色的barrier的到来,而黄色barrier之前的数据将会对缓存的数据相加
  11. 这次处理的总结:分界线对齐,barrier 向下游传递,sum 任务会等待所有输入分区的 barrier 到达,对于barrier已经到达的分区,继续到达的数据会被缓存。而barrier尚未到达的分区,数据会被正常处理
    在这里插入图片描述
  12. 当蓝色的barrier和黄色的barrier(所有分区的)都到达后,进行状态保存到远程仓库,然后对JobManager发送消息,说自己的检查点保存完毕了
  13. 此时的偶数流和奇数流都为8
  14. 当收到所有输入分区的 barrier 时,任务就将其状态保存到状态后端的检查点中,然后将 barrier 继续向下游转发
    在这里插入图片描述
  15. 向下游转发检查点 barrier 后,任务继续正常的数据处理
    在这里插入图片描述
  16. Sink 任务向 JobManager 确认状态保存到 checkpoint 完毕
  17. 当所有任务都确认已成功将状态保存到检查点时,检查点就真正完成了
    在这里插入图片描述

保存点

原则上,保存点的生成算法和检查点完全一样,因此可以把保存点看做包含一些额外元数据的检查点。保存点的生成不是由Flink自动完成,而是需要由用户(外部调度器)显式触发。同时,Flink也不会自动清理保存点。