其他分享
首页 > 其他分享> > Flink前世今生和原理

Flink前世今生和原理

作者:互联网

1. 基本概念

Why-分布式计算发展史

为什么需要流式计算,为什么需要Flink,是需要从分布式计算的历史开始说。

随着大数据时代到来,单机的计算已经不能满足数据计算的需求,将多个计算机组成集群去处理一个问题的方案成为主流,即分布式计算。而分布式系统的发展也伴随批处理向流处理的演进过程

MapReduce

首先是MapReduce的分布式计算编程范式,它通过将一个问题拆分为多个子问题,并在多个机器上求解,同时机器间进行数据交换和数据合并,最终输出结果。基于这种模式诞生了Hadoop框架,是较早的批处理框架。集团内产品对应ODPS,我们平时使用Hive SQL其底层执行基于MapReduce

其对应的过程大致如下:

  1. split分片。首先一个任务由client提交给JobTracker组件,JobTracker指定TaskTracker完成对应任务。数据源一般是HDFS,他会把HDFS上存储的block(基本单位)合并并分发给不同的map task做为任务的数据源,这也是“分而治之”的分
  2. map映射。主要是是将数据映射成key-value的数据结构,在map task中数据将会经历下面几个过程:
  1. 每个map task都有一个内存缓冲区(一般100MB),数据会先转化为key-value数据结构并存在内存中,接着通过对应的Partitioner策略对数据进行分组(默认hash后为reduce task的数量取膜),用于决定数据最终由哪个reduce task处理
  2. 当数据量超过内存80%后会溢出(spill),此时新写入的使用剩余的20%内存,溢出的数据会以文件形式写入磁盘。
  1. 溢写程序执行的过程他会对key做排序(sort),期间为了减少传输/存储成本会根据Combine的策略合并(combine)部分数据,类似提前reduce
  2. 当多次溢出形成了多个溢出文件,需要将这些文件归并在一起形成一个大文件,这个过程是合并(Merge),最终根据不同partition将文件拆分发给对应的reduce task

  1. reduce归约。reduce task任务执行前会通过HTTP请求不断地拉取不同map task产生的属于自己分区的结果文件,同时做类似溢写的操作将内存的数据跟磁盘数据做merge,其中也伴随combine,reduce的shuffle动作在最终文件形成之时结束。后续则是reducer执行并将结果输出到HDFS上

我们可以看到Hadoop的MapReduce的过程中存在一些问题:

这些问题随着实时性场景的增多逐渐暴露出来,往往数据都要隔日产生,一开始大家并没有在意,直到Spark的出现

Spark

针对MR这些问题诞生了Spark、Tez等DAG框架,其初衷是改良Hadoop的MapReduce编程模型和执行速度。与MapReduce相比Spark有了这些优势:

Hadoop MapReduce 简单粗暴地根据 shuffle 将大数据计算分成 Map 和 Reduce 两个阶段,然后就算完事了。而 Spark 更细腻一点,将前一个的 Reduce 和后一个的 Map 连接起来,当作一个阶段持续计算,形成一个更加优雅、高效的计算模型,虽然其本质依然是 Map 和 Reduce。

但是这种多个计算阶段依赖执行的方案可以有效减少对 HDFS 的访问,减少作业的调度执行次数,因此执行速度也更快

因为这些特征,Spark在有界数据批处理方面一直处于统治地位。但是现实中,数据往往是源源不断产生的,划分一部分历史数据进行批量处理往往没法满足实时的需求。有没有方式可以无需针对整个数据集操作,而对通过系统传输的每个数据项执行操作呢,流处理应运而生。

尽管Spark后期也尝试引入扩展Spark Streaming,希望将批处理粒度变小改为微批处理,但其核心还是批处理的思想

Flink

想较于批处理处理速度更快的流处理框架逐渐盛行。Storm可以以极低的延迟处理数据,成为了流处理的先驱。但是Core Storm无法保证消息的处理顺序,且消息提供了“至少一次”的处理保证,这意味着消息可以重复处理,对于一些要求数据正确性的业务场景是不能接受的。

Flink框架的出现解决了这些问题,并具有以下优势:

总结

三种编程范式:

流式计算两种类型:

三种主流流式计算框架区别:

功能指标

Storm

Spark

Flink

处理模式

native

micro-batching

native

消息保障

至少一次

有且一次

有且一次

实时性

低延迟,亚秒级

高延迟,秒级

低延迟,亚秒级

吞吐量

高(Storm3-5倍)

流量控制

不支持

支持

支持

容错方式

record ack

rdd based check point

check point

What-什么是Flink

Flink是什么,官方的定义是在无边界和有边界数据流上进行有状态的计算的框架和分布式处理引擎

Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams.

无边界和有边界:数据在系统中是实时源源不断产生的,有边界在Flink中指的如下有限流覆盖的一块系统历史数据的集合,它的数量在一开始就是确认的,一旦确认不可改变;而无边界数据是持续产生的数据,输入是无限的没有终止时间,需要以特定的时间顺序处理

有状态计算:所谓的有状态计算就是要结合历史信息进行的计算。例如对于反欺诈行为的识别,要根据用户在近几分钟之内的行为做出判断。一旦出现异常,就需要重新执行流计算任务,但重新处理所有的原始数据是不现实的,而Flink的容错机制和State能够使Flink的流计算作业恢复到近期的一个时间点,从这个时间点开始执行流计算任务,这无疑能够大大降低大规模任务失败恢复的成本。

Where-Flink应用场景

Flink的应用场景:

事件驱动型应用

传统事务分层架构,会监听事件并在读写公共的事务数据库再去触发相应动作,计算和存储是分离的;而基于Flink的事件驱动型应用,通过监听事件的日志并写入/更新状态到应用本地,同时触发动作/输出新事件给下游,期间定期向远程持久化存储的checkpoint,这种方式的计算和存储是不分离的。

对于那些不在意中间过程的事件型驱动适合这种模式,而像平时业务型应用还是建议采用传统架构。具体应用如下:

数据分析应用

区分传统的批处理,将数据批量导入再统一分析存在一定的延迟且存在数据有界性,而流处理的引入可以根据事件的产生实时更新,在交易中HSF依赖健康分析就是Flink这个场景的应用,实时关联应用上下游的调用情况分析各个服务间的强弱依赖关系,进而做出预警措施。

具体应用有:

数据管道应用

通过Flink的SQL接口可以实现实时的ETL工作,将上游数据进行处理后输出下游

应用有:

原理

什么是流?

流可以理解为上面讲到的不断产生的无边界的数据,而Flink则是处理这种数据的流处理框架。那么数据流在Flink中是什么形式存在的,又如何流转和变化,最终结果是怎么输出的

Dataflow模型

Flink的流模型参考了Dataflow模型,它是一套准确可靠的关于流处理的解决方案。在Dataflow模型提出以前,流处理常被认为是一种不可靠但低延迟的处理方式,需要配合类似于MapReduce的准确但高延迟的批处理框架才能得到一个可靠的结果(Lambda架构)。

背景:

起初,Dataflow模型是为了解决Google的广告变现问题而设计的。因为广告主需要实时的知道自己投放的广告播放、观看情况等指标从而更好的进行决策,但是批处理框架Mapreduce、Spark等无法满足时延的要求(因为它们需要等待所有的数据成为一个批次后才会开始处理),(当时)新生的流处理框架Aurora、Niagara等还没经受大规模生产环境的考验,饱经考验的流处理框架Storm、Samza却没有“恰好一次”的准确性保障(在广告投放时,如果播放量算多一次,意味广告主的亏损,导致对平台的不信任,而少算一次则是平台的亏损,平台方很难接受),DStreaming(Spark1.X)无法处理事件时间,只有基于记录数或基于数据处理时间的窗口,Lambda架构过于复杂且可维护性低,最契合的Flink在当时并未成熟。最后Google只能基于MillWheel重新审视流的概念设计出Dataflow模型和Google Cloud Dataflow框架,并最终影响了Spark 2.x和Flink的发展,也促使了Apache Beam项目的开源。

Dataflow由三个部分组成:

Flink程序执行时,由流和转换操作映射到streaming dataflows,每个数据流有1个或多个 source,有一个或多个sink,这个数据流最终形成一个DAG。

上述的是一个逻辑图,实际在执行Dataflow程序的时候需要将他转换为物理Dataflow图。

首先Flink本身是分布式的,在实际执行时一个Stream(流)有多个Stream Partition(流分区),一个operator(算子)也会有多个operator subtasks(子算子任务),他可以让不同算子子任务在不同机器上执行以达到分布式能力。一个算子并行任务的个数叫做算子的并行度。并行度可以通过程序中重写来修改

对于两个算子间流的流转来说有两种模式:

比如上述Flink程序逻辑,对应输入source并行度为2则会有两个source的算子子任务,map和keyBy等算子并行度也是2,而最终输出的Sink并行度为1意味着结果最终汇聚在一个节点输出,最终Parallel Dataflows图如下:

进一步,Flink的Dataflow执行按层级分为4层:

State状态

与批计算相比,State是流计算特有的,批计算没有failover机制,要么成功,要么重新计算。

上面说到数据流以单个记录维度流转在Flink搭建的算子DAG中,当数据到达某些算子节点时会转换成另外的格式流转到下一个算子,但是有的算子是需要做聚合计算或模式匹配的,我们不能来一个数据就将历史数据重做一次,所以保存原来计算结果在原来结果上做增量计算就很有必要。Flink中的持久化模型就是State,实现为RocksDB本地文件+异步HDFS持久化,后改为基于Niagara的分布式存储

当我们考虑一下使用批处理系统来分析一个无界数据集时,会发现状态的重要性显而易见。在现代流处理器兴起之前,处理无界数据集的一个通常做法是将输入的事件攒成微批,然后交由批处理器来处理。当一个任务结束时,计算结果将被持久化,而所有的运算符状态就丢失了。一旦一个任务在计算下一个微批次的数据时,这个任务是无法访问上一个任务的状态的(都丢掉了)。这个问题通常使用将状态代理到外部系统(例如数据库)的方法来解决。相反,在一个连续不间断运行的流处理任务中,事件的状态是一直存在的,我们可以将状态暴露出来作为编程模型中的一等公民。当然,我们的确可以使用外部系统来管理流的状态,即使这个解决方案会带来额外的延迟。

State分为两类:

以一次双流JOIN的过程来理解State:参考 https://ata.alibaba-inc.com/articles/109200

双流INNER JOIN两边事件都会存储到State里面,如上,事件流按照标号先后流入到join节点,我们假设右边流比较快,先流入了3个事件,3个事件会存储到state中,但因为左边还没有数据,所有右边前3个事件流入时候,没有join结果流出,当左边第一个事件序号为4的流入时候,先存储左边state,再与右边已经流入的3个事件进行join,join的结果如图 三行结果会流入到下游节点sink。当第5号事件流入时候,也会和左边第4号事件进行join,流出一条jion结果到下游节点。ps:对于left join会增加一个撤回动作,针对已经发到下游但是右表内容传null的情况。

State扩容机制:

常见算子

算子

实现

备注

map:

无状态算子。输入一个元素,然后返回一个元素

flatmap

无状态算子。 输入一个元素,可以返回零个,一个或者多个元素

filter

对流进行过滤,符合条件的数据会被留下

join

有状态算子。两边事件存储在State上,数据结构为Map<JoinKey, Map<rowData, count>其中count记录相同事件个数

https://ata.alibaba-inc.com/articles/109200

数据关联相关都会用到双流JOIN

group by

有状态算子。通常会维护一个TreeMap内存结构用于存储TopN数据,以及MapState结构用于failover后恢复

https://ata.alibaba-inc.com/articles/94592

可以做TopN

时间概念

流处理跟批处理很大的不同是他需要引入时间的概念,因为数据的产生是无界且连续的,我们需要给予他们秩序,Flink中增加了一个新的时间维度用于管理流上的事件,通过时间的概念打通流和批,但同时给数据处理也带来了复杂性。

Flink定义了三种数据时间概念:

Watermarks

引入时间主要为了解决几个问题,首先是数据乱序延迟到达的问题,在分布式系统下很常见,Flink主要通过水位线机制解决。

水位线(Watermarks是全局进度的度量标准。系统可以确信在一个时间点之后,不会有早于这个时间点发生的事件到来了。本质上,水位线提供了一个逻辑时钟,这个逻辑时钟告诉系统当前的事件时间。当一个运算符接收到含有时间T的水位线时,这个运算符会认为早于时间T的发生的事件已经全部都到达了。对于事件时间窗口和乱序事件的处理,水位线非常重要。运算符一旦接收到水位线,运算符会认为一段时间内发生的所有事件都已经观察到,可以触发针对这段时间内所有事件的计算了

ps:对于多个输入源的算子,Watermarks的传递上会选择所有输入源中目前达到的最低值传递给下一个算子

水位线提供了一种结果可信度和延时之间的妥协。激进的水位线设置可以保证低延迟,但结果的准确性不够;如果水位线设置的过于宽松,计算的结果准确性会很高,但可能会增加流处理程序不必要的延时。通过在数据源上设置 WATERMARK wk1 FOR event_time as withOffset(event_time, 5000)来控制延迟范围

对于一些场景对延迟和速度重视大于准确性时可以使用处理时间作为标准,这时候就不需要感知乱序/迟到,只在机器时间满足时去触发计算即可。

Window

为了能够得到例如中值之类的统计结果,我们需要给定一个边界,Flink引入Window(窗口)机制用于处理有界的数据,这里窗口可以是基于数据属性(Count Window 最近100个事件)也可以基于时间(Time Window,如过去24小时)。主要下面种窗口类型:

系统容错

流计算容错一致性保证有三种,分别是:

Checkpointing检查点

Flink中基于异步轻量级的分布式快照技术提供了Checkpoints容错机制,通过它的恢复机制保证了Exactly once状态一致性。

Checkpoints可以将同一时间点作业/算子的状态数据全局统一快照处理,包括前面提到的算子状态和键值分区状态。当发生了故障后,Flink会将所有任务的状态恢复至最后一次Checkpoint中的状态,并从那里重新开始执行。这跟MySQL之类的checkpoint不大一样,Mysql会通过redolog增量存储恢复时重做,但是Flink则是从checkpoint点重新计算,因为它存储了输入流上一次消费的位置。

其过程有就像Watermarks的传递一样,Flink会在流上定期产生一个barrier(屏障)。barrier 是一个轻量的,用于标记stream顺序的数据结构。barrier被插入到数据流中,作为数据流的一部分和数据一起向下流动,过程如下:

  1. barrier 由source节点发出;
  2. barrier会将流上event切分到不同的checkpoint中;
  3. 汇聚到当前节点的多流的barrier要对齐(At least once不需要对齐);
  4. barrier对齐之后会进行Checkpointing,生成snapshot,快照保存到StateBackend中;
  5. 完成snapshot之后向下游发出barrier,继续直到Sink节点;

以上是Flink系统内Exactly-once语义的实现。如果想实现端到端(Soruce到Sink)的Exactly-once语义,需要外部Source和Sink的支持,比如Source要支持精准的offset,Sink要支持两阶段提交

retract回撤

运行架构

集群架构

Flink整体架构图如下:

高可用方面,如果TaskManager挂了,JobManager会监测到错误并向ResourceManager申请新的资源,如果没有足够资源将没发重启。对于JobManager来说,它会将元数据信息存储在外部存储,而在Zookeeper集群中保存一份指向存储的指针,当JobManager宕机会先取消任务,然后会有新的JobManager启动并接替它的工作。

任务执行

如上图所示,每个Worker (TaskManager) 是一个JVM进程,可以在单独的线程中执行一个或多个子任务。为了控制一个TaskManager接受多少任务,一个TaskManager有至少一个Task Slot(任务槽)。TaskManager会划分内存分配给各个Task Slot,由于在同一个JVM他们会共享TCP连接、心跳消息、CPU资源。

流控

Flink的流量控制基于信任度

接收任务授予发送任务一些“信任度”(credit),也就是为了接收其数据而保留的网络缓冲区数。当发送者收到一个信任度通知,它就会按照被授予的信任度,发送尽可能多的缓冲数据,并且同时发送目前积压数据的大小——也就是已填满并准备发送的网络缓冲的数量。接收者用保留的缓冲区处理发来的数据,并对发送者传来的积压量进行综合考量,为其所有连接的发送者确定下一个信用度授权的优先级。

基于信用度的流控制可以减少延迟,因为发送者可以在接收者有足够的资源接受数据时立即发送数据。此外,在数据倾斜的情况下,这样分配网络资源是一种很有效的机制,因为信用度是根据发送者积压数据量的规模授予的。因此,基于信用的流量控制是Flink实现高吞吐量和低延迟的重要组成部分。

使用

Flink的API分层如下,我们经常开发中使用的是它最高层抽象的API,即声明式编程 -- SQL

 

标签:今生,处理,Flink,TaskManager,前世,算子,Spark,数据
来源: https://blog.csdn.net/wu_noah/article/details/120685619