阿里云大学Apache Flink大数据学习笔记

          之前有看过一些基础的大数据课程,现在又回来发现这里的课程更新还是蛮快的,讲的内容干货也很多,继续学习一下,下面是一些主要内容说明。

地址:https://developer.aliyun.com/learning/course/58?spm=a2c6h.17661847.0.0.3b266fb2aXVsbc

第一节    Flink 中文社区发起人开课寄语

内容介绍:

1.大数据趋势

2.Apache Flink 的介绍

3.Apache Flink 在中国的应用

一、大数据的趋势

“容量大 Volume”

“多样性 Variety”

“价值高 Value”

“速度快 Velocity”

二、Apache Flink 的介绍

“apache 是 web 应用服务器,可以运行在几乎所有广泛使用的计算机平台上,它快速、可靠并且可通过简单的 API 扩充,将 html、php、jsp、perl、Python 编写的 web 程序解析、编译到服务器中。”

第二节   《第一课,走进 Apache Flink》

内容介绍

1.什么是Apache Flink

2.为什么要学习Apache Flink

3.Apache Flink典型应用场景

4.Apache Flink基本概念

       Flink核心是一个流式的数据流执行引擎,其针对数据流的分布式计算提供了数据分布、数据通信以及容错机制等功能。基于流执行引擎,Flink提供了诸多更高抽象层的API以便用户编写分布式任务:

DataSet API, 对静态数据进行批处理操作,将静态数据抽象成分布式的数据集,用户可以方便地使用Flink提供的各种操作符对分布式数据集进行处理,支持Java、Scala和Python。

DataStream API,对数据流进行流处理操作,将流式的数据抽象成分布式的数据流,用户可以方便地对分布式数据流进行各种操作,支持Java和Scala。

Table API,对结构化数据进行查询操作,将结构化数据抽象成关系表,并通过类SQL的DSL对关系表进行各种查询操作,支持Java和Scala。

Flink的起源

起源于柏林工业大学的实验室,最开始的名称是Stratosphere,项目的目标是为了让大数据的初拟元变得更加简洁。

Flink的发展

从2014年五月份开始进入孵化期,到2014年12月12日称为Apache顶级项目。

Flink的现状——Apache社区最活跃的项目.

Flink 是一个开源的分布式流式处理框架:

①提供准确的结果,甚至在出现无序或者延迟加载的数据的情况下。

②它是状态化的容错的,同时在维护一次完整的的应用状态时,能无缝修复错误。

③大规模运行,在上千个节点运行时有很好的吞吐量和低延迟。

更早的时候,我们讨论了数据集类型(有界 vs 无穷)和运算模型(批处理 vs 流式)的匹配。Flink 的流式计算模型启用了很多功能特性,如状态管理,处理无序数据,灵活的视窗,这些功能对于得出无穷数据集的精确结果是很重要的。

除了提供数据驱动的视窗外,Flink还支持基于时间,计数,session等的灵活视窗。视窗能够灵活的触发条件定制化从而达到对复杂的流传输模式的支持。Flink的视窗使得模拟真实的创建数据的环境成为可能。

大数据处理的实时化趋势

Flink已成为国内外实时计算事实标准

流计算引擎的演进

设计理念:可以保证低延迟

3.   Apache Flink典型应用场景

事件驱动型应用

在社交、网购、金融等场景都能起到识别、检测的作用,从而针对特定的情况触发一系列的指令。

数据分析应用

进行信息的汇总,包括对一些营销策略的效果分析

双十一的时候,阿里每秒的消息处理高达40亿条。

数据管道型应用(ETL)

 ETL(Extract-Transform-Load)从数据源抽取/转换/加载/数据至目的端的过程。

支持Multi-Sink几百个内置函数

多种内置聚合函数支持Join/Window语义

多源

噪音(异常数据分流/规则过滤/统一格式)

聚合Reduce数据

4.Apache Flink基本概念

Flink的核心概念

Event Streams实时的或历史的事件流

State有状态计算复杂业务逻辑

(Event)Time数据乱序场景下的一致性处理

Snapshots检查点作业升级/迁移

Flink 作业描述和逻辑拓扑

Flink状态管理和快照

Flink API

Process Function

class MyFunction extends ProcessFunction[MyEvent,Result] {

// declare state to use in the program

Lazy val state: valuestate[CountwithTimestamp] =getRuntimeContext( ).getstate..)def processElement(event: MyEvent,ctx: Context,out: collector[Result]): Unit ={

//work with event and state

(event, state.value) match ( -. }

out.collect() //emit events

state.update(// modify state

//schedule a timer callback

ctx.timerService.registerEventTimeTimer(event.timestamp + see)

)

def onTimer(timestamp: Long,ctx: OnTimerContext,out: collector[Result] ): unit = {

//handle callback when event-/processing- time instant is reached

}

}

Table API & Stream SQL

// TabLe API

val tapiResult: Table=tEnv.scan("sensors")

//scansensors table

.window(Tumble over 1.hour on 'rowtime as 'w)

// define 1-hour window

.groupBy( 'w, 'room)

//group by window and room

.select( ' room,'w.end,'temp.avg as 'avgTemp)

 // compute average temperature

SELECT room,TUNBLE_END( rowtime,INTERVAL '1’HOUR),AVG(temp) AS avgTemp

FROM sensors

GROUP BY TUMBLE( rowtime,INTERVAL '1’ HOUR),room

第三节   《Stream Processing with Apache Flink》

内容介绍

1.并行处理和编程范式

2.DataStream API概览

3.状态和时间

1.并行处理和编程范式

并行计算和DAG

举例:批阅试卷,共有A、B、C三个题,多人相互协作,按组划分,按试卷划分,就是所谓的计算并行性和数据并行性。

可以将图中的节点划分为三部分

Source

Transformation

Sink

命令式编程和声明式编程

命令式

public static int imperative(){

List<Integer> tempList = new ArrayList<> ();

for (int v : data) {

tempList.add (v * 2);

}

int result = 0 ;

for (int v : tempList) {

result += v;

}

return result;

}

声明式

public static int declarative() {

return data.stream ( ) .mapToInt (v -> v * 2) .sum ( ) ;

}

SELECT SUM(2 * value) FROM data

2.DataStream API概览

Flink API逻辑层次

Flink作业产生的过程

DaTaStream转换操作

整体转换图

说明:

winodw: 单并发处理

keyBy: 多平发处理

KeyBy 操作只有当 Key 的数量超过算子的并发实例数才可以较好的工作。由于同一个 Key 对应的所有数据都会发送到同一个实例上,因此如果Key 的数量比实例数量少时,就会导致部分实例收不到数据,从而导致计算能力不能充分发挥。

分区策略

类型

描述

dataStream.keyByo()

按照key值区分

dataStream.global()

全部发往第一个实例

dataStream.broadcast()

广播

dataStream.forward()

上下游并行度一样时一对一发送

dataStream.shuffle()

随机均匀分配

dataStream.rebalance()

Round-Robin(轮流分配)

dataStream.rescale()

Local Round-Robin(本地轮流分配)

Flink连接器

外部系统:文件系统、数据库、消息队列.......

Source是否支持监测并接入更新(Dynamic or static)

Sink是否支持更新已有结果(Append-only or support update)

 

3.状态和时间

有状态的计算

Flink中的状态原语

抛弃编程语言中原生的数据容器(List、Map),使用Flink状态原语

KeyedState使用方法

1.只能用于RichFunction

2.将State声明为实例变量

3.在open()方法中为State赋值

  • 创建一个StateDescriptor
  • 利用getRuntimeContext().getState(...)获得State

4.调用State的方法进行读写(例如: state.value().state.update(...))

Flink中的时间

1、事件时间(Event Time):

事件时间是每个独立事件在产生它的设备上发生的时间,这个时间在事件进入Flink之前就已经嵌入到事件中,时间顺序取决于事件产生的地方,和下游数据处理系统的时间无关。

2、接入时间(Ingestion Time)

接入时间是数据进入Flink系统的时间,接入时间依赖Source Operator 所在主机的系统时钟。因为接入时间在数据接入过程生成后,时间戳不在发生变化,和后续处理数据的Operator所在机器的时钟没有关系,所以不会因为某台机器时钟不同步或网络延迟而导致计算结果不准确的问题。相比于Event Time,Ingestion Time 不能处理乱序事件,因此不用生成对应的Watermarks.

3、处理时间(Processing Time)

处理时间是指数据在操作算子计算过程中获取到的所在主机。当用户选择使用Processing Time 时,所有和时间相关的计算算子,例如Windows计算,在当前的任务中所有算子将直接使用其所在主机的系统时间。基于Processing Time 时间概念,Flink 的程序性能相对较高,延迟也比较低,对接入到系统中的数据时间相关的计算完全交给算子内部决定。虽然性能和易用性上有优势,但在处理数据乱序时,Processing Time 不是最优的选择,数据本身不乱序,如果每台机器本身的时钟不同步也会导致数据处理过程中出现数据乱序,Processing Time 适用于时间计算精度不是特别高的计算场景。

4、时间概念的指定

在Flink 中默认情况下使用Processing Time ,如果想用其它的时间类型,则在创建StreamExecutionEnvironment 中调用setStreamTimeCharacteristic()设定时间概念。

Processing Time和 Event Time

Timestamp分配和Watermark生成

在SourceFunction中产生

collectWithTimestamp(T element, long timestamp)

emitWatermark(Watermark mark)

在流程中指定

DataStream.assignTimestampsAndWatermarks(...)

定期生成

根据特殊记录生成

实现时间驱动

数据驱动

每隔一段时间调用生成方法

每一次分配Timestamp都会调用生成方法

实现AssignerWithPeriodicWatermarks

实现AssignerWithPunctuatedWatermarks

时间相关API

目标

Event Time

Processing Time

获取记录事件

context.getTimestamp0或从数据字段中获取

timerService().currentProcessingTime()

获取“watermark"

timerService().currentWatermark()

timerService().currentProcessingTime()

注册定时器

timerService.registerEventTimeTimer()

timerService.registerProcessingTimeTimer()

第四节   《Flink Runtime Architecture》

内容介绍:

1.Runtime总览

2.作业的控制中心- JobMaster

3.任务的运行容器- TaskExecutor

4.资源的管理中心 – ResourceManager

Runtime总览

众所周知 Flink 是分布式的数据处理框架,用户的业务逻辑会以Job的形式提交给 Flink 集群。Flink Runtime作为 Flink 引擎,负责让这些作业能够跑起来并正常完结。

这些作业既可以是流计算作业,也可以是批处理作业,既可以跑在裸机上,也可以在Flink集群上跑,Flink Runtime必须支持所有类型的作业,以及不同条件下运行的作业。

1.分布式数据处理引擎

2.作业的表达

用户通过API的方式写一个作业,例如上图左侧StreamWordInput的示例,它可以不断的输出一个个单词;下面的Map操作负责把单词映射成一个二元组;再接一个keyBy,使相同的word的二元组都被分配在一起,然后sum将它们计数,最后打印出来。

左侧的作业对应着右边的逻辑拓扑(StreamGraph)。这个拓扑中有4个节点,分别是source、map、sum和print。这些是数据处理逻辑,又称之为算子;节点之间的线条对应着数据的分发方式,影响着数据以什么样的方式分发给下游。举例来说,map到sum之间是keyBy,意味着map产出的数据,同一个key的数据都必须分发到同一个下游。

有了StreamGraph后, Flink Runtime会进一步的把它翻译成JobGraph。JobGraph和StreamGraph的区别是,JobGraph会把一些节点 chain 起来,形成Operator chain。Chain条件是需要两个算子的并发度是一样的,并且它们的数据交换方式是一对一的。 形成的Operator chain,又称为JobVertex。

3.分布式架构

Flink 作为分布式数据处理框架,它有一套分布式的架构,主要分为三块:Client、Master和Worker节点。

Master是 Flink 集群的主控中心,它可以有一个到多个JobMaster,每个JobMaster对应一个作业,而这些JobMaster由一个叫Dispatcher的控件统一管理。Master节点中还有一个ResourceManager进行资源管理。

ResourceManager管理着所有Worker节点,它同时服务于所有作业。此外Master节点中还有一个Rest Server,它会用于响应各种Client端来的Rest请求,Client端包括Web端以及命令行的客户端,它可以发起的请求包括提交作业、查询作业的状态和停止作业等等。作业会通过执行图被划分成一个个的任务,这些任务最后都会在Worker节点中进行执行。Worker就是TaskExecutor,它们是任务执行的容器。

作业执行的核心组件有三个,分别是JobMaster、TaskExecutor和ResourceManager:JobMaster用于管理作业;TaskExecutor用于执行各个任务;ResourceManager用于管理资源,并服务于JobMaster的资源请求。

JobMaster -作业的控制中心

主要职责:
1.作业生命周期管理

2.任务调度

3.出错恢复

4.作业状态查询

5.分布式状态快照

JobMaster中的核心组件是Scheduler,无论是作业的生命周期管理、作业的状态维护,还是任务的调度以及出错恢复,都是由Schedule来负责的。

1.作业的生命周期

正常流程下作业会有三种状态,分别是Created、Running和Finished。一个作业开始是处于Created的状态,当这个作业被开始调度就开始进入Running状态并开始调度任务,等到所有的任务都成功结束了,这个作业就走到Finished的状态,并汇报最终结果,然后退出。

然而,一个作业在执行过程中可能会遇到一些问题,因此作业也会有异常处理的状态。作业执行过程中如果出现作业级别错误,整个作业会进到Failing状态,然后Cancel所有任务。

等到所有任务都进入最终状态后,包括 Failed、Canceled、Finished,再去check出错的异常。如果异常是不可恢复的,那么整个作业会走到Failed状态并退出。

如果异常是可恢复的,那么会走到Restarting状态,来尝试重启。如果重启的次数没有超过上限,作业会从Created状态重新进行调度;如果达到上限,作业会走到Failed状态并退出。

 (注:在 Flink 1.10 之后的版本中,当发生错误时,如果可以恢复,作业不会进入 Failing 状态而会直接进入 Restarting 状态,当所有任务都恢复正常后,作业会回到 Running 状态。如果作业无法恢复,则作业会经由 Failing 状态最终进入 Failed 状态并结束。)

Cancelling和Canceled两种状态只会在用户手动去Cancel作业的时候走到。当用户手动的在Web UI或通过 Flink command探索作业的时候, Flink 会首先把状态转到Cancel里,然后Cancel所有任务,等所有任务都进入最终状态后,整个作业就会进入Canceled状态并退出。

Suspended状态只会在配置了high availability,并且当JobMaster丢掉leadership才会走到。这个状态只意味着这个JobMaster出现问题终止了。一般来说等到JobMaster重新拿到leadership之后,或是另外有一个standby Master拿到leadership之后,会在拿到leadership的节点上重新启动起来。

2.任务调度

调度策略(SchedulingStrategy)控制调度的时机

事件驱动

作业开始调度

任务状态变化

任务产出的数据可消费

失败任务需要重启

多种调度策略

Eager

 Lazy from sources

(WIP) Pipelined region based

目前我们有多种不同的调度策略,分别是Eager和Lazy from sources。EagerSchedulingStrategy主要是服务于流式作业,它的策略是在作业开始调度时,直接启动所有的任务,这样做的好处是可以降低调度时间。Lazy from sources主要服务于批处理作业。它的策略是作业一开始只调度Source节点,等到有任意节点的输入数据可以被消费后,它才会被调起来。如下图所示,source节点的数据开始产出后,agg节点才能被调起来,agg节点结束后,sink节点才能被调起来。

3.出错恢复

重启出错失败的任务以及可能受其影响的任务-停止相关任务

FAILED/CANCELED

重置任务状态-CREATED

通知调度策略重新调度

由出错恢复策略(FailoverStrategy)决定需要重启的任务

RestartPipelinedRegionFailoverStrategy

RestartAllFailoverStrategy

4.单点重启

TaskExecutor -任务的运行容器

TaskExecutor是任务的运行器,为了运行任务它具有各种各样的资源。如下图所示,这里主要介绍memory的资源。

TaskExecutor 的资源模型

Process memory

JvMmemory

Flink memory

Framework memory.

Task memory

Task Slot 对应Task资源的子集,目前
为TE的资源按照slot数量进行均分

Task Heap memory

Task Off-heap memory

Network memory

Managed memory

Task Slot是任务运行的逻辑容器,需要满足任务的资源需求

1.Task Slot共享
同一个共享组(SlotSharingGroup)中的不同类型(JobVertex)的任务,可以同时在一个slot中运行

降低数据交换开销

方便用户配置资源

均衡负载

2.任务执行模型

任务(Task)在一个独占的线程中执行

Task 从InputGate中读取数据,喂给OperatorChain ,
OperatorChain会将产出数据输出到 ResultPartition中

Source task直接通过SourceFunction产出数据

ResourceManager -资源的管理中心

ResourceManager是 Flink 的资源管理中心。在前面我们有提到过TaskExecutor包含了各种各样的资源。而ResourceManager,就管理着这些TaskExecutor。新启动的TaskExecutor,需要向ResourceManager进行注册,之后它里边的资源才能服务于作业的请求。

ResourceManager管理TaskExecutor

SlotManager管理Slot

通过TE-RM心跳更新Slot状态,心跳信息中包含TE中所有的slot状态

Slot申请流程

JM -> RM -> TE ->JM,以 slot offer的结果为准

ResourceManager的多种实现

StandaloneResourceManager

手动拉起Worker

YarnResourceManager

MesosResourceManager

KubernetesResourceManager

Slot申请过程中自动拉起Worker

第五节     《Fault-tolerance in Flink》

内容介绍:

一、有状态的流计算

二、全局一致性快照

三、Flink的容错机制

四、Flink的状态管理

一、有状态的流计算

1.流计算

流计算是指有一个数据源可以持续不断地发送消息,同时有一个常驻程序运行代码,从数据源拿到一个消息后会进行处理,然后把结果输出到下游。

2.分布式流计算

 

 

分布式流计算是指把输入流以某种方式进行一个划分,再使用多个分布式实例对流进行处理。

3.流计算中的状态

计算可以分成有状态和无状态两种,无状态的计算只需要处理单一事件,有状态的计算需要记录并处理多个事件。

举个简单的例子:

例如一个事件由事件ID和事件值两部分组成,如果处理逻辑是每拿到一个事件,都解析并输出它的事件值,那么这就是一个无状态的计算;相反,如果每拿到一个状态,解析它的值出来后,需要和前一个事件值进行比较,比前一个事件值大的时候才把它进行输出,这就是一个有状态的计算。

流计算中的状态有很多种。比如在去重的场景下,会记录所有的主键;又或者在窗口计算里,已经进入窗口还没触发的数据,这也是流计算的状态。

在机器学习/深度学习场景里,训练的模型及参数数据都是流计算的状态。

二、全局一致性快照

全局一致性快照是可以用来给分布式系统做备份和故障恢复的机制。

1.全局快照

什么是全局快照

全局快照首先是一个分布式应用,它有多个进程分布在多个服务器上;

其次,它在应用内部有自己的处理逻辑和状态;

第三,应用间是可以互相通信的;

第四,在这种分布式的应用,有内部状态,硬件可以通信的情况下,某一时刻的全局状态,就叫做全局的快照。

为什么需要全局快照

第一,用它来做检查点,可以定期对全局状态做备份,当应用程序故障时,就可以拿来恢复;

第二,做死锁检测,进行快照后当前的程序继续运行,然后可以对快照进行分析,看应用程序是不是存在死锁状态,如果是就可以进行相应的处理。

全局快照举例

下图为分布式系统中全局快照的示例。

P1和P2是两个进程,它们之间有消息发送的管道,分别是C12和C21。

对于 P1进程来说, C12是它发送消息的管道,称作output channel; C21是它接收消息的管道,称作 input channel。

除了管道,每个进程都有一个本地的状态。

比如说P1和P2每个进程的内存里都有XYZ三个变量和相应的值。

那么 P1和P2进程的本地状态和它们之间发送消息的管道状态,就是一个初始的全局状态,也可称为全局快照。

假设P1给P2发了一条消息,让P2把x的状态值从4改为7,但是这个消息在管道中,还没到达P2。

这个状态也是一个全局快照。

再接下来,P2收到了P1的消息,但是还没有处理,这个状态也是一个全局快照。

最后接到消息的P2把本地的X的值从4改为7,这也是一个全局快照。

所以当有事件发生的时候,全局的状态就会发生改变。事件包括进程发送消息、进程接收消息和进程修改自己的状态。

2.全局一致性快照

当事件发生时,全局的状态会发生改变,这里的事件包括:

-进程发送消息

-进程接收到消息

-进程修改状态

a->b代表在绝对时钟(real time)下a happened before b,则当一个全局快照满足下述条件时,我们称其为一个全局一致性快照︰

-如果A->B且B被包含在该快照中,则A也被包含在这个快照中

假如说有两个事件,a和b,在绝对时间下,如果a发生在b之前,且b被包含在快照当中,那么则a也被包含在快照当中。

满足这个条件的全局快照,就称为全局一致性快照。

3.全局一致性快照的实现方法

 

时钟同步并不能实现全局一致性快照;全局同步虽然可以实现,但是它的缺点也非常明显,它会让所有应用程序都停下来,会影响全局的性能。

4.异步全局一致性快照算法 – Chandy-Lamport

异步全局一致性快照算法Chandy-Lamport可以在不影响应用程序运行的前提下,实现全局一致性快照。

Chandy-Lamport的系统要求有以下几点:

第一,不影响应用运行,也就是不影响收发消息,不需要停止应用程序;

第二,每个进程都可以记录本地状态;

第三,可以分布式地对已记录的状态进行收集;

第四,任意进程都可以发起快照

同时,Chandy-Lamport算法可以执行还有一个前提条件:消息有序且不重复,并且消息可靠性可保障。

Chandy-Lamport算法流程

Chandy-Lamport的算法流程主要分为三个部分:

发起快照、分布式的执行快照和终止快照   。

发起快照

任意进程都可以发起快照。

如下图所示,当由P1发起快照的时候,第一步需要记录本地的状态,也就是对本地进行快照,然后立刻向它所有 output channel发送一个marker消息,这中间是没有时间间隙的。

marker消息是一个特殊的消息,它不同于应用之间传递的消息。

发出Marker消息后,P1就会开始记录所有input channel的消息,也就是图示C21管道的消息。

分布式的执行快照

如下图,先假定当 Pi接收到来自Cki的marker消息。

也就是Pk发给Pi的marker消息。可以分两种情况来看:

第一种情况:

这个是Pi收到的第一个来自其它管道的marker消息,它会先记录一下本地的状态,再把 C12管道记为空,也就是说后续再从 P1发消息,就不包含在此次快照里了,与此同时立刻向它所有output channel发送marker消息。

最后开始记录来自除Cki之外的所有input channel的消息。

上面提到Cki消息不包含在实时快照里,但是实时消息还是会发生,所以第二种情况是,如果此前Pi已经接收过marker消息,它会停止记录 Cki消息,同时会将此前记录的所有Cki消息作为Cki在本次快照中的最终状态来保存。

终止快照

终止快照的条件有两个:

第一,所有进程都已经接收到marker消息,并记录在本地快照;

第二,所有进程都从它的n-1个input channel里收到了marker 消息,并记录了管道状态。

当快照终止,快照收集器 (Central Server) 就开始收集每一个部分的快照去形成全局一致性快照了。

示例展示

在下图的例子里,一些状态是在内部发生的,比如A,它跟其它进程没有交互。内部状态就是 P1发给自己消息,可以将A认为是C11=[A->]。

Chandy-Lamport全局一致性快照的算法是怎么执行的呢?

假设从p1来发起快照,它发起快照时,首先对本地的状态进行快照,称之为S1,然后立刻向它所有的output channel,即P2和P3,分别发marker消息,然后再去记录它所有input channel的消息,即来自P2和P3及自身的消息。

图例所示,纵轴是绝对时间,按照绝对时间来看,为什么P3和P2收到marker消息会有时间差呢?

因为假如这是一个真实的物理环境里的分布式进程,不同节点之间的网络状况是不一样的,这种情况会导致消息送达时间存在差异。

P3先收到marker消息,且是它接收到的第一个marker消息。

接收到消息后,它首先会对本地状态进行快照,然后把 C13管道的标记成 close,与此同时开始向它所有的output channel发送 marker消息。

最后它会把来自除了C13之外的所有input channel的消息开始进行记录。

接收到P3发出的marker信息的是P1,但这不是它接收的第一个marker,它会把来自C31 channel的管道立刻关闭,并且把当前的记录消息做这个channel的快照,后续再接收到来自P3的消息,就不会更新在此次的快照状态里了。

接下来P2接收到来自P3的消息,这是它接到的第一个marker消息。

接收到消息后,它首先对本地状态进行快照,然后把 C32管道的标记成 close,与此同时开始向它所有的output channel发送 marker消息,最后它会把来自除了C32之外的所有input channel的消息开始进行记录。

再来看P2接收到来自P1的消息,这不是P2接收到的第一个marker消息,所以它会把所有的 input channel全部关闭,并且记录channel的状态。

接下来看P1接收到来自P2的消息,这也不是它接收的第一个消息。那么它就会把所有的input channel关闭,并把记录的消息作为状态。那么这里面有两个状态,一个是C11,即自己发给自己的消息;一个是C21,是P2里H发给P1D的。

最后一个时间点,P3接收到来自P2的消息,这也不是它收到的第一个消息,操作跟上面介绍的一样。

在这期间P3本地有一个事件J,它也会把J作为它的状态。

当所有进程都记录了本地状态,而且每一个进程的所有输入管道都已经关闭了,那么全局一致性快照就结束了,也就是对过去时间点的全局性的状态记录完成了。

Chandy-Lamport与 Flink之间的关系

Flink 是分布式系统,所以 Flink 会采用全局一致性快照的方式形成检查点,来支持故障恢复。

Flink的异步全局一致性快照算法跟Chandy-Lamport算法的区别主要有以下几点:

第一,Chandy-Lamput支持强连通图,而 Flink支持弱连通图;

第二,Flink采用的是裁剪的(Tailored)Chandy-Lamput异步快照算法;

第三,Flink的异步快照算法在DAG场景下不需要存储Channel state,从而极大减少快照的存储空间。

三、Flink的容错机制

容错,就是恢复到出错前的状态。流计算容错一致性保证有三种,分别是:

Exactly once,At least once,At most once。

1.Exactly once,是指每条event会且只会对state产生一次影响,这里的“一次”并非端到端的严格一次,而是指在 Flink内部只处理一次,不包括source和sink的处理。

2.At least once,是指每条event会对state产生最少一次影响,也就是存在重复处理的可能。

3.At most once,是指每条event会对state产生最多一次影响,就是状态可能会在出错时丢失。

端到端的Exactly once

Exactly once的意思是,作业结果总是正确的,但是很可能产出多次;所以它的要求是需要有可重放的source。

端到端的Exactly once,是指作业结果正确且只会被产出一次,它的要求除了有可重放的source外,还要求有事务型的sink和可以接收幂等的产出结果。

Flink的状态容错

简单场景的 Exactly Once 容错方法

简单场景的做法如下图,方法就是,记录本地状态并且把 source的offset,即 Event log的位置记录下来就好了。

分布式场景的状态容错

如果是分布式场景,我们需要在不中断运算的前提下对多个拥有本地状态的算子产生全局一致性快照。

Flink 分布式场景的作业拓扑比较特殊,它是有向无环并且是弱联通图,可以采用裁剪的Chandy-Lamport,也就是只记录所有输入的offset和各个算子状态,并依赖rewindable source(可回溯的source,即可以通过offset读取比较早一点时间点),从而不需要存储channel的状态,这在存在聚合 (aggregation)逻辑的情况下可以节省大量的存储空间。

最后做恢复,恢复就是把数据源的位置重新设定,然后每一个算子都从检查点恢复状态。

3.Flink 的分布式快照方法

首先在源数据流里插入Checkpoint barrier,也就是上文提到的Chandy-Lamport算法里的marker message,不同的Checkpoint barrier会把流自然地切分多个段,每个段都包含了Checkpoint的数据;

Flink 里有一个全局的 Coordinator,它不像 Chandy-Lamport 对任意一个进程都可以发起快照,这个集中式的 Coordinator会把 Checkpoint barrier 注入到每个 source 里,然后启动快照。

当每个节点收到 barrier 后,因为 Flink 里面它不存储 Channel state,所以它只需存储本地的状态就好。

在做完了Checkpoint 后,每个算子的每个并发都会向Coordinator发送一个确认消息,当所有任务的确认消息都被Checkpoint Coordinator接收,快照就结束了。

4.流程演示

见下图示,假设Checkpoint N 被注入到 source里,这时source会先把它正在处理分区的offset记录下来。

随着时间的流逝,它会把Checkpoint barrier发送到两个并发的下游,当barrier分别到达两个并发,这两个并发会分别把它们本地的状态都记录在Checkpoint 的里:

最后barrier到达最终的subtask,快照就完成了。

这是比较简单的场景演示,每个算子只有单流的输入,再来看下图比较复杂的场景,算子有多流输入的情况。

当算子有多个输入,需要把Barrier 对齐。怎么把Barrier对齐呢?

如下图所示,在左侧原本的状态下,当其中一条barrier到达,另一条barrier命令上有的barrier还在管道中没有到达,这时会在保证Exactly once的情况下,把先到达的流直接阻塞掉,然后等待另一条流的数据处理。等到另外一条流也到达了,会把之前的流unblock,同时把barrier发送到算子。

在这个过程中,阻塞掉其中一条流的作用是,会让它产生反压。Barrier 对齐会导致反压和暂停operator的数据处理。

如果不在对齐过程中阻塞已收到barrier的数据管道,数据持续不断流进来,那么属于下个Checkpoint的数据被包含在当前的Checkpoint里,如果一旦发生故障恢复后,由于source会被rewind,部分数据会有重复处理,这就是at-least-once。 如果能接收at-least-once,那么可以选择其他可以避免barrier对齐带来的副作用。

另外也可以通过异步快照来尽量减少任务停顿并支持多个Checkpoint同时进行。

5.快照触发

本地快照同步上传到系统需要state Copy-on-write的机制。

假如对元数据信息做了快照之后数据处理恢复了,在上传数据的过程中如何保证恢复的应用程序逻辑不会修改正在上传的数据呢?

实际上不同状态存储后端的处理是不一样的,Heap backend会触发数据的copy-on-write,而对于RocksDB backend来说LSM的特性可以保证已经快照的数据不会被修改。

四、Flink 的状态管理

1.Flink 状态管理

首先需要去定义一个状态,在下图的例子里,先定义一个Value state。

在定义的状态的时候,需要给出以下的几个信息:

1.状态识别ID

2.状态数据类型

3.本地状态后端注册状态

4.本地状态后端读写状态

2.Flink 状态后端

也称作state backend,Flink状态后端有两种;

第一种,JVM Heap,它里面的数据是以Java对象形式存在的,读写也是以对象形式去完成的,所以速度很快。

但是也存在两个弊端:第一个弊端,以对象方式存储所需的空间是磁盘上序列化压缩后的数据大小的很多倍,所以占用的内存空间很大;第二个弊端,虽然读写不用做序列化,但是在形成snapshot时需要做序列化,所以它的异步snapshot过程会比较慢。

第二种,RocksDB,这个类型在读写时就需要做序列化,所以它读写的速度比较慢。

但是它有一个好处,基于 LSM 的数据结构在快照之后会形成sst文件,它的异步 checkpoint 过程就是文件拷贝的过程,CPU 消耗会比较低。

第六节     《Flink SQL / Table 介绍与实战》

内容介绍:

1.背景

2.Flink SQL & Table API

3.DEMO

背景

Flink有非常强大的API抽象能力,它提供了三层的API,从底至上分别是Process Function, DataStream API以及SQL和Table API。这三层都有不同的用户群体,越低层灵活度越高,门槛也会越高,最高层门槛较低,但是会牺牲一些灵活度。

Flink强大的抽象能力

为什么要花精力做SQL和Table API?

DataStream API非常好用,因为它的表达能力非常强,用户可以维护和更新应用状态,而且它对时间的控制力也非常灵活。但相对而言,它的复杂度和门槛也更高,并不适用于所有人,很多用户希望专注于业务逻辑。所以,要提供更加简单易懂的API,SQL是目前最佳的选择。

Flink SQL和Table API优势有很多。首先它非常易于理解,很多不同行业不同领域的人都懂SQL,它已经成为大数据处理生态圈的标准语言了;其次SQL是声明式的语言,用户只需要表达想要什么,而无需关心如何计算;然后SQL是会自动优化的,能生成最优的执行计划;同时SQL还是30多年的语言,非常稳定;最后,SQL可以更容易地统一流和批,用同一套系统就能同时处理,让用户只关注最核心的业务逻辑。

Flink SQL & Table API的优势

SQL和Table API 简介

Flink关系式的API主要暴露两种,一种是SQL的API,还有一种是Table的API。

SQL API完全遵循ANSI SQL的标准设计,所以如果有SQL基础,它的学习门槛是比较低的,而Table可以理解为类SQL的编程式的API。他们都是统一的批处理和流处理的API,不管输入是静态的批处理数据,还是无限的流处理数据,他的查询的结果都是相同的。总结而言,就是一份代码,一个结果,这也是流批统一的最重要的评价指标。

Flink的工作流程

         下面是比较高级的概览图,SQL和Table在进入Flink以后会转化成统一的数据结构表达形式,即Logical Plan。其中,Catalog会提供一些原数据信息,用于后续的优化。Logical Plan是优化的路口,经过一系列的优化规则后,Flink会把初始的Logical Plan优化为Physical Plan,并通过Code Generation机制翻译为Transformation,最后转换成JobGraph,用于提交到 Flink的集群做分布式的执行。

可以看到,整个流程并没有单独的流处理和批处理的路径,因为这些优化的过程和扩建都是共享的。

用实例理解流和批

比如一个点击的文件,有user、点击的时间和URL。如果要统计点击的次数,在选出user做统一的批处理的情况下,它的特点就是一次性读入和一次性输出。

而如果Click是一个数据流,在这种情况下,输入一条数据后就能输出一个结果,比如Marry第一次点击会记录一次,第二次点击就会做增量计算。所以输入数据会持续读入,结果也会持续被更新。

可以看到,这里流和批的结果是一样的,所以可以把以前批处理的SQL迁移到Flink上做流处理,它的结果和语义应该和之前的批处理是一样的。

Flink SQL & Table API

典型的包括低延迟ETL处理,比如数据的预处理、清洗和过滤;还有数据管道,Flink可以做实时和离线的数据管道,可以构建低延时实时数仓,也可以实时数据同步,把数据从某一个数据系统同步到另一个数据系统;

第三种是流式和批式的数据分析,去计算和更新离线或实时的数据,并进行可视化,典型的比如阿里双11的大屏;

最后一种是模式识别,也就是实时地识别数据流中符合某种pattern的事件流,然后做相应的监控或者报警的服务,比如网约车的一些异常事件的监控服务。

Flink SQL/Table应用案例

低延迟ETL处理

数据管道,构建低延时实时数仓,实时数据同步

流式&批式的数据分析

计算更新实时/离线数据并可视化、

模式识别

网约车异常事件监测服务

Flink的核心功能

下图包含了Flink的一些核心功能。第一个是SQL的DDL,DDL直接对接外部系统,它的强弱决定了Flink与外部系统的联通性,而作为一个计算引擎,与外部数据存储的联通性非常重要;第二是完整的类型系统,它支持多种数据类型,这对SQL引擎而言也是非常必要的;第三是高效流式TopN,有非常强大的流处理能力,用来实时计算排行旁,比如双11的销量排行榜;还有高效的流式去重对数据进行过滤,因为有时采集会包含重复的数据;还有维表关联、对接CDC等。

除此之外,Flink还有非常多的内置函数,支持MiniBatch,以及有多种解热点手段。它还支持完整的批处理,适用Python等语言,还有Hive的集成等功能,不仅能直接访问Hive的数据,还兼容了Hive的语法,让用户不必再频繁切换。

下面是一个电商的用户行为的实时分析。从Kafka中实时地消费用户的行为数据,并与MySQL中的数据进行关联,然后写入Elasticsearch的索引中,并用Kibana进行视觉化呈现。这是一个端到端的实时应用的构建。

下面是来自某宝的用户行为日志,选取了11月27日当天的行为,它包含这几个字段,包括用户ID、商品ID、商品类目ID、行为类型和时间戳。行为类型中,pv代表点击,buy代表购买,cart代表加入购物车,fav代表收藏事件,而时间戳代表事件发生的时间。

实战演练

首先,新建一个目录,比如叫flink-sql-demo,然后把docker-compose的demo文件下载下来,可以点进去看一下这个文件。

这里面有个dategen的数据源,可以去控制它的产生速度,比如把产生的速度从2000改成3000。

通过docker-compose up-d把docker中的容器都启动起来。容器包括Jobmanager、Taskmanager这两个Flink的集群,还有Kibana、Elasticsearch、Zookeeper、MySQL、Kafka等。

可以用Docker-compose的命令看一下Kafka中最新的10条数据。它有user ID,有商品ID,有类目ID,有用户的行为,还有一个TS代表这个行为当时发生的时间,

随后启动今天的主角,通过Docker-compose启动SQL-Client容器,当看到这个松鼠的时候,SQL Client就成功启动了,我们可以在里面运行SQL的命令。

第一步要用DDL创建数据源,把用户日志的数据源先创建起来。用Create Table这个DDL语法创建了一个user behavior的表,它里面有5个字段,包括user ID,商品ID,类目ID,用户行为和TS时间戳。With里面跟的是一些如何连接到外部系统的属性,比如用Kafka连接外部的topic。

另外也能通过show table看user behavior,用describe table看表的结构、字段、计算列、watermark策略等等。

接下来用3个实战去画一些图表,深入了解Flink的一些功能。

首先是统计每小时的成交量。先用DDL创建Elasticsearch表,定义每小时的成交量,随后提交Query去做每小时成交量的统计分析。

需要做每小时的一个滑窗,用到Tumble Window语法。Tumble的第一个字段定义时间属性,也就是刚刚说的TS事件时间,然后开窗大小是一小时,每小时会滑一个窗口,然后对窗口内的数据做统计分析。

提交这个Query,然后通过5601端口访问Kibana对它进行可视化。刚进来的时候是空的,里面什么数据都没有,所以我们一般要先创建 create index pattern,通过页面的Management中的Index Pattern进入,找到索引,点击进去创建。

创建了Index Pattern以后,才能在当中做一些Discovery或者可视化。可以看到,这些字段就是刚才DDL里定义的,并且它还有对应的值。

最终是要进行可视化,所以要创建一个Dashboard。在页面左上角点击Dashboard,然后点击Create New创建新的视图,随后就可以设置每小时的成交量了。

画一个面积图,在Y轴上选择购买量max,标签名字改为“成交量”,然后因为X轴展示时间,所以选择“hour-of-day”,order by字母序,改为24,随后点击播放键,面积图就画好了。也可以点击保存,这样这个面积图就会保存到Dashboard上。

随后再画一个图,统计一天每10分钟累计独立用户数。同样,需要现在SQL CLI中创建一个Elasticsearch表,用于存储结果汇总数据,字符段包括日期时间和累计uv数。

然后,在SQL CLI中执行Table。里面Query主要做了一件事,就是把日期和时间选出来。这里唯一有一点特殊的是,因为需求是做每10分钟的点,所以用Substr的两个竖线做连接的函数来实现。随后,像之前一样把Query提交到SQL CLI里面运

在Y轴取uv的值,命名为“独立访问用户数”,X轴选terms,然后选time-str,order by Alphabetical,一天中改为150个点。然后点播放,独立用户数的曲线图就出现了。同样,可以点击保存,把这个图加到Dashboard上。

随后来画第三张图。第三张图是顶级类目排行榜,因为一个商品对应的类目太细分,比如对应非常细的第三四级类目,所以它对排行榜意义可能不大。但是希望归约到一个顶级类目做统计分析,所以开始之前准备了MySQL的容器,里面准备了子类目和顶级类目的映射关系。

首先在SQL CLI中创建MySQL表,后续用作维表查询,同时再创建一个Elasticsearch表,用于存储类目统计结果。而在Query这里,会用到Create View语法去注册一个临时的视图,简化写法,因为把两个Query写在一起可能会比较复杂。

同样在SQL CLI中运行代码,然后进入到Kibana页面建立索引和添加可视化图表。这里使用Horizontal Bar去画一个柱形图。在Y轴上,统计类目的成交量,X轴用类目的名字,排序用倒序排列,随后点击播放键。最后,同样点击保存,把类目排行榜添加到Dashboard上。加上之前做的两个,Dashboard上就有3个图表了,这里可以自己拖拽图表美化一下。

第七课    《PyFlink 快速上手》

内容介绍:

PyFlink介绍

PyFlink相关功能

PyFlink功能演示

PyFlink下一步规划

PyFlink介绍

PyFlink是Flink的一个子模块,也是整个Flink项目的一部分,主要目的是提供Flink的Python语言支持。

因为在机器学习和数据分析等领域,Python语言非常重要,甚至是最主要的开发语言。所以,为了满足更多用户需求,拓宽Flink的生态,我们启动了PyFlink项目。

PyFlink项目的目标主要有两点

第一点是将Flink的计算能力输出给Python用户,也就是我们会在Flink中提供一系列的Python API,方便对Python语言比较熟悉的用户开发Flink作业。

第二点,就是将Python生态基于Flink进行分布式化。虽然我们会在Flink中提供一系列的Python API来给Python用户来使用,但这对用户来说是有学习成本的,因为用户要学习怎么使用Flink的Python API,了解每一个API的用途。所以我们希望用户能在API层使用他们比较熟悉的 Python库的API,但是底层的计算引擎使用Flink,从而降低他们的学习成本。这是我们未来要做的事情,目前处于启动阶段。

PyFlink项目的发展情况,目前发布了3个版本,支持的内容也越来越丰富。

PyFlink相关功能介绍

我们主要介绍PyFlink以下功能,Python Table API、Python UDF、向量化Python UDF、Python UDF Metrics、PyFlink依赖管理和Python UDF执行优化。

Python Table API

Python Table API的目的是为了让用户可以使用Python语言来开发Flink作业。

Flink里面有三种类型的API,Process、Function和Table API,前两者是较为底层的API,基于Process和Function开发的作业,其逻辑会严格按照用户定义的行为进行执行,而Table API是较为高层的API,基于Table API开发的作业,其逻辑会经过一系列的优化之后进行执行。

Python Table API,顾名思义就是提供 Table API的Python语言支持。

以下是Python Table API开发的一个Flink作业,作业逻辑是读取文件,计算word count,然后再把计算结果写到文件中去。这个例子虽然简单,但包括了开发一个Python Table API作业的所有基本流程。

首先我们需要定义作业的执行模式,比如说是批模式还是流模式,作业的并发度是多少?作业的配置是什么。接下来我们需要定义source表和sink表,source表定义了作业的数据源来源于哪里,数据的格式是什么;sink表定义了作业的执行结果写到哪里去,数据格式是什么。最后我们需要定义作业的执行逻辑,在这个例子中是计算写过来的count。

以下是Python Table API的部分截图,可以看到它的数量和功能都比较齐全。

Python UDF

Python Table API是一种关系型的API,其功能可以类比成SQL,而SQL里自定义函数是非常重要的功能,可以极大地扩展SQL的使用范围。

Python UDF的主要目的就是允许用户使用Python语言来开发自定义函数,从而扩展Python Table API的使用场景。同时,Python UDF除了可以用在Python Table API作业中之外,还可以用在Java Table API作业以及SQL作业中。

在PyFlink中我们支持多种方式来定义Python UDF。用户可以定义一个Python类,继承ScalarFunction,也可以定义一个普通的Python函数或者Lambda函数,实现自定义函数的逻辑。除此之外,我们还支持通过Callable Function和Partial Function定义Python UDF。用户可以根据自己的需要选择最适合自己的方式。

PyFlink里面提供了多种Python UDF的使用方式,包括Python Table API、Java table API和SQL,我们一一介绍。

在Python Table API中使用Python UDF,在定义完Python UDF之后,用户首先需要注册Python UDF,可以调用table environment register来注册,然后命名,然后就可以在作业中通过这个名字来使用 Python UDF了。

Python Table API作业中使用Python UDF

table_env.register_function("function_name", python_udf)

在Java Table API中它的使用方式也比较相似,但是注册方式不一样,Java Table API作业中需要通过DDL语句来进行注册。

除此之外,用户也可以在SQL的作业中使用Python UDF。与前面两种方式类似,用户首先需要注册Python UDF,可以在SQL脚本中通过DDL语句来注册,也可以在SQL Client的环境配置文件里面注册。

Python UDF –如何使用. sQL作业中使用Python UDF

  • 注册Python UDF

sql-client.yaml

方式1: create temporary system function add_one as 'udfs.add_one' language python;方式2: sQL client环境配置文件

Python UDF架构

简单介绍下Python UDF的执行架构。Flink是用Java语言编写的,运行在Java虚拟机中,而Python UDF运行在 Python虚拟机中,所以Java进程和Python进程需要进行数据通信。 除此之外,两者间还需要传输state、log、metrics,它们的传输协议需要支持4种类型。

向量化Python UDF

向量化Python UDF的主要目的是使 Python用户可以利用Pandas或者Numpy等数据分析领域常用的Python库,开发高性能的Python UDF。

向量化Python UDF是相对于普通Python UDF而言的,我们可以在下图看到两者的区别。

向量化Python UDF的执行过程。首先在Java端,Java在攒完多条数据之后会转换成Arrow格式,然后发送给Python进程。Python进程在收到数据之后,将其转换成Pandas的数据结构,然后调用用户自定义的向量化Python UDF。同时向量化Python UDF的执行结果会再转化成Arrow格式的数据,再发送给 Java进程。

在使用方式上,向量化Python UDF与普通Python UDF是类似的,只有以下几个地方稍有不同。首先向量化Python UDF的声明方式需要加一个UDF type,声明这是一个向量化Python UDF,同时UDF的输入输出类型是Pandas Series。

Python UDF Metrics

前面我们提到 Python UDF有多种定义方式,但是如果需要在Python UDF中使用Metrics,那么Python UDF必须继承ScalarFunction来进行定义。在Python UDF的 open方法里面提供了一个Function Context参数,用户可以通过Function Context参数来注册Metrics,然后就可以通过注册的 Metrics对象来汇报了。

PyFlink依赖管理

从类型来说,PyFlink依赖主要包括以下几种类型,普通的PyFlink文件、存档文件,第三方的库、PyFlink解释器,或者Java的Jar包等等。从解决方案来看,针对每种类型的依赖,PyFlink提供了两种解决方案,一种是API的解决方案,一种是命令行选项的方式,大家选择其一即可。

Python UDF执行优化

Python UDF的执行优化主要包括两个方面,执行计划优化和运行时优化。它与SQL非常像,一个包含Python UDF的作业,首先会经过预先定义的规则,生成一个最优的执行计划。在执行计划已经确定的情况下,在实际执行的时候,又可以运用一些其他的优化手段来达到尽可能高的执行效率。

Python UDF执行计划优化

计划的优化主要有以下几个优化思路。一个是不同类型的 UDF的拆分,由于在一个节点中可能同时包含多种类型的UDF,而不同的类型的UDF是不能放在一块执行的;第二个方面是Filter下推,其主要目的是尽可能降低含有Python UDF节点的输入数据量,从而提升整个作业的执行性能;第三个优化思路是Python UDF Chaining,Java进程与Python进程之间的通信开销以及序列化反序列化开销比较大,而Python UDF Chaining可以尽量减少Java进程和Python进程之间的通信开销。

假如有这样一个作业,它包含了两个UDF,其中add是Python UDF, subtract是向量化Python UDF。默认情况下,这个作业的执行计划会有一个project节点,这两个 UDF同时位于这一project的节点里面。这个执行计划的主要问题是,普通Python UDF每次处理一条数据,而向量化Python UDF,每次处理多条数据,所以这样的一个执行计划是没有办法执行的。

但是通过拆分,我们可以把这一个project的节点拆分成了两个project的节点,其中第一个project的节点只包含普通Python UDF,而第二个节点只包含向量化Python UDF。不同类型的Python UDF拆分到不同的节点之后,每一个节点都只包含了一种类型的UDF,所以算子就可以根据它所包含的UDF的类型选择最合适的执行方式。

Filter下推到Python UDF之前

Filter下推的主要目的是将过滤算子下推到Python UDF节点之前,尽量减少Python UDF节点的数据量。

假如我们有这样一个作业,作业原始执行计划里面包括了两个Project的节点,一个是add、 subtract,同时还包括一个Filter节点。这个执行计划是可以运行的,但需要更优化。可以看到,因为Python的节点位于Filter节点之前,所以在Filter节点之前Python UDF已经计算完了,但是如果把Filter过滤下,推到Python UDF之前,那么就可以大大降低Python UDF节点的输入数据量。

Python UDF Chaining

假如我们有这样一个作业,里面包含两种类型的UDF,一个是add,一个是subtract,它们都是普通的Python UDF。在一个执行计划里面包含两个project的节点,其中第一个project的节点先算subtract,然后再传输给第二个project节点进行执行。

它的主要问题是,由于subtract和add位于两个不同的节点,其计算结果需要从Python发送回Java,然后再由Java进程发送给第二个节点的Python进行执行。相当于数据在Java进程和Python进程之间转了一圈,所以它带来了完全没有必要的通信开销和序列化反序列化开销。因此,我们可以将执行计划优化成右图,就是将add节点和subtract节点放在一个节点中运行,subtract节点的结果计算出来之后直接去调用add节点。

Python UDF运行时优化

目前提高Python UDF运营时的执行效率有三种:一是Cython优化,用它来提高Python代码的执行效率;二是自定义Java进程和Python进程之间的序列化器和反序列化器,提高序列化和反序列化效率;三是提供向量化Python UDF功能。

PyFlink相关功能演示

https://github.com/pyflink/playgrounds/tree/1.11

首先大家打开这个页面,里面提供了PyFlink的一些demo,这些demo是运行在docker里面的,所以大家如果要运行这些demo就需要在本机安装docker环境。

随后,我们可以运行命令,命令会启动一个PyFlink的集群,后面我们运行的PyFlink的例子都会提交到集群去执行。

第一个例子是word count,我们首先在里面定义了环境、source、sink等,我们可以运行一下这个作业。

这是作业的执行结果,可以看到Flink这个单词出现了两次,PyFlink这个单词出现了一次。

接下来再运行一个Python UDF的例子。这个例子和前面有一些类似,首先我们定义它使用PyFlink,运行在批这种模式下,同时作业的并发度是1。不一样的地方是我们在作业里定义了一个UDF,它的输入包括两个列,都是Bigint类型,而且它输出类型也是对应的。这个UDF的逻辑是把这两个列的相加作为一个结果输出。

我们执行一下作业,执行结果是3。

接下来我们再运行一个带有依赖的Python UDF。前面作业的UDF是不包含任何依赖的,直接就把两个输入列相加起来。而在这个例子里,UDF引用了一个第三方的依赖,我们可以通过API set python requirement来执行。

接下来我们运行作业,它的执行结果和前面是一样的,因为这两个作业的逻辑是类似的。

接下来我们再看一个向量化Python UDF的例子。在 UDF定义的时候,我们加了一个UDF的type字段,说明说我们是一个向量化的Python UDF,其他的逻辑和普通Python UDF的逻辑类似。最后它的执行结果也是3,因为它的逻辑和前面是一样的,计算两页的之和。

我们再来看一个例子,在Java的Table作业里面使用Python。在这个作业里面我们又会用到一个Python UDF,它通过DDL语句进行注册,然后在execute SQL语句里面进行使用。

接下来我们再看在纯SQL作业中使用Python UDF的例子。在资源文件里面我们声明了一个UDF,名字叫add1,它的类型是Python,同时我们也能看到它的UDF位置。

接下来我们运行它,执行结果是234。

yFlink下一步规划

目前PyFlink只支持了Python Table API,我们计划在下一个版本中支持DataStream API,同时也会支持Python UDAF以及Pandas UDAF,另外,在执行层也会持续优化PyFlink的执行效率。

第八节      《Flink Ecosystems》

内容介绍:

一、Flink SQL连接外部系统的实现原理

二、Flink SQL常用的Connector

一、Flink SQL连接外部系统的实现原理

在讲原理之前,我们先回答为什么要使用Flink SQL?SQL是一个标准化的数据查询语言,而在Flink SQL中,我们可以通过Catalog与各种系统集成,同时我们也开发了很丰富的内置操作符和函数,而且Flink SQL还可以同时处理批数据和流数据,能极大地提高数据分析的工作效率。

那么Flink SQL为什么又要对接外部系统呢?Flink SQL本身是一个流计算的引擎,它本身不维护任何数据,所以对Flink SQL而言,所有的数据都存储在外部系统当中,也就是所有的表都是在外部系统中,我们只有对接这些外部系统,才能够对数据进行实际的读写。

Flink SQL对接外部系统

何时该使用SQL

通过catalog方便与各种系统集成

丰富的内置操作符和函数

同一套查询处理批和流数据

何时不该使用sQL

需要控制状态和窗口的情况,如自定义timer等

跨版本的savepoint兼容性目前无法保证

表总是存储在外部系统中

在讲解Flink SQL如何与外部系统对接之前,我们先看一下Flink内部DataStream和Table是如何做转换的?假设已经有一个DataStream程序了,那么我们可以把它转换成Table的方式来使用,用Flink SQL的一些强大功能对它进行查询,可以通过下列例子理解,类似于Flink SQL内部的对接。

Connector

对于Flink SQL而言,对接外部系统的组件被称作Connector。下面这张表里列出了Flink SQL所支持的几个比较常用的Connector,比如Filesystem对接的是文件系统,JDBC对接的是外部的关系型数据库等等。每一个Connector主要负责实现一个source和一个sink, source负责从外部系统中读数据,sink负责把数据写入到外部系统中。

Format

Format指定了数据在外部系统中的格式,比如一个Kafka的表,它里面的数据可能是CSV格式存储的,也有可能是JSON格式存储的,所以我们在指定一个Connector连接外部表的时候,通常也需要指定Format是什么,这样Flink 才能正确地去读写这个数据。

Catalog

Catalog可以连接外部系统的元数据,然后把元数据信息提供给Flink,这样Flink可以直接去访问外部系统中已经创建好的表或者database等等。比如Hive的元数据是存储在Hive Metastore中的,那么Flink如果想访问Hive表的话,就有一个HiveCatalog来对接元数据。除此之外,它还可以帮助Flink 来持久化它自身的元数据。比如说HiveCatalog既可以帮Flink 来访问Hive,也可以帮Flink来存储一些Flink所创建的表的信息,这样就不需要每次启动Session的时候重新建表了,直接去读取Hive Metastore中建好的表就可以了。

如何创建一张表来指定外部的 connector?

下面的例子是通过DDL来创建的一张表,这是一个比较标准的Create Table语句,其中所有跟Connector相关的参数都在with语句当中指定,比如这里的Connector等于Kafka等等。

当通过DDL创建了一张表后,这个表是如何在Flink当中被使用的?这里有一个很关键的概念就是Table Factory。在这个黄色的框里面,我们可以通过DDL建表,或者可以通过Catalog从外部系统中拿到,然后被转化成Catalog Table对象。当我们在SQL语句中引用Catalog Table时,Flink会为这张表创建对应的source或者是sink,创建source和sink的这个模块儿就叫做Table Factory。

获取Table Factory的方式有两种,一个是Catalog本身绑定了一个Table Factory,另一种是通过Java的SPI来确定Table Factory,但是它查找的时候要正好有一个配对才不会报错。

Flink SQL常用的Connector

Kafka Connector

Kafka Connector是用得最多的,因为Flink是一个流计算的引擎,而Kafka又是最流行的消息队列,所以用Flink的用户大部分也都在用Kafka。如果我们要创建Kafka的表,就需要指定一些特定的参数,比如将Connector字段指定成Kafka,还有Kafka对应的topic等,我们可以在下图看到这些参数及其所代表的的含义。

要使用Kafka Connector,就需要添加Kafka一些依赖的Jar包,根据所使用的Kafka版本不一样,添加的Jar包也不太一样,这些Jar包都可以在官网上下载到。

Elasticsearch Connector

Elasticsearch Connector只实现了Sink,所以只能往ES里去写,而不能从里面读。它的Connector类型可以指定成ES6或者ES7;Hosts就是指定的ES的各个节点,通过域名加端口号的形式;Index是指定写ES的index,类似于传统数据库当中的一张表;Document Type类似于传统数据库的表里面的某一行,不过在ES7里不需要指定。

ES的Sink支持append和upsert两种模式,如果这张ES表在定义的时候指定了PK,那么Sink就会以upsert模式工作,如果没有指定PK,就以append模式来工作,但是像ROW和MAP等类型是不能作为PK的。

Key Handling

  • Elasticsearch的sink支持append模式和lupsert模式·目标表定义了PK—upsert模式
  • 目标表没有PK—append模式
  • Upsert模式时,用所有的PK生成document lD用户可以指定key delimiter
  • 某些类型的列不能作为PK
    • ROw、MAP等

同样,使用ES也需要指定额外的依赖,针对不同的ES版本添加ES Connector。

FileSystem Connector

  • 对应Flink的FileSystem抽象
  • 支持Local、Hadoop、S3、oss等·支持分区
  • 采用与Hive相似的分区目录结构·分区信息不需要注册到catalog中
  • 支持Streaming Sink,暂不支持Streaming Source.FileSystem Connector本身不需要添加额外的依赖
  • 可能需要特定FileSystem实现的依赖

.可能需要特定Format的依赖

这个Connector对接的是一个文件系统,它读写的是这个文件系统上的文件。这里所说的FileSystem指的是Flink的FileSystem抽象,它支持很多种不同的实现,比如支持本地文件系统、Hadoop、S3、OSS等不同的实现。同时它还支持分区,采取与Hive相似的分区目录结构,但分区信息不需要注册到Catalog中。

Hive Connector

  • 通过HiveCatalog对接Hive元数据
  • 通过HiveTableSource、HiveTableSink读写Hive表数据·与Hive的兼容性
  • 支持多Hive版本,1.0.0~3.1.2
  • 支持多种文件格式,text,ORC,Parquet,SequenceFile,RCFile等·支持多种数据类型

. Hive Dialect提供Hive风格语法-Hive批流一体数仓

. Streaming Sink,Streaming Source,Lookup Join

Hive应该是最早的SQL引擎,在批处理场景中大部分用户都在使用。Hive Connector可以分为两个层面,首先在元数据上,我们通过HiveCatalog来对接Hive元数据,同时我们提供HiveTableSource、HiveTableSink来读写 Hive的表数据。

使用Hive Connector需要指定Hive Catalog,这里是一个例子,展示如何指定Hive Catalog。

execution:

planner: blinktype: streaming...

current-catalog: myhive # set the HiveCatalog as the current catalog of the sessioncurrent-database: mydatabase

catalogs :

name: myhivetype: hive

hive-conf-dir: /opt/hive-conf # contains hive-site.xml

使用Hive Connector也需要添加一些额外的依赖,大家可以根据所使用的Hive版本来选择对应的Jar包。

除了连接外部系统外,我们也有内置的Connector,它们一方面是帮助新的用户能够尽快地上手,体验Flink SQL强大的功能,另一方面也能帮助Flink的开发人员做一些代码的调试。

DataGen Connector

DataGen Connector是一个数据生成器。比如这里创建了一个DataGen的表,指定了几个字段。把Connector的类型指定成DataGen,这个时候去读这张表,Connector会负责生成数据,也就是说数据是生成出来的,并不是事先要存储在某个地方。然后用户可以对DataGen      Connector做一些比较细粒度的控制,比如可以指定每秒钟生成多少行数据,然后某个字段可以指定它通过sequence也就是从小到大来创建,也可以指定通过random的方式来创建等等。

Print Connector

Print Connector提供Sink功能,负责把所有的数据打印到标准输出或者标准错误输出上,打印的格式是前面会带一个row kind。创建 print的表的时候只需要把Connector类型指定成print就可以了。

BlackHole Connector

BlackHole Connector也是一个Sink,它会丢弃掉所有的数据,也就是说数据写过来它什么都不做就丢掉了,主要是可以用来做性能的测试。创建BlackHole你只需要把Connector类型指定成BlackHole就可以了。

第九节   《Flink Connector详解》

内容介绍:

1.连接器Connector概述

2.Source API

3.Sink API

4.未来发展

一、连接器Connector概述

Flink是新一代流批统一的计算引擎,它需要从不同的第三方存储引擎中把数据读过来,进行处理,然后再写出到另外的存储引擎中。Connector的作用就相当于一个连接器,连接 Flink 计算引擎跟外界存储系统。

Flink里有以下几种方式,当然也不限于这几种方式可以跟外界进行数据交换:

第一种 Flink里面预定义了一些source和sink。

第二种 FLink内部也提供了一些Boundled connectors。

第三种 可以使用第三方apache Bahir项目中提供的连接器。

第四种是通过异步IO方式。

核心抽象:

1.记录分布

有编号的记录集台

进度可追踪

记录分片的所有信息

2.记录分片枚举器SplitEnumerator

发现记录分片

分配记录分片

协调Source 读取器

3.Source读取器SourceReader

从记录分片读取数据

事件时间水印处理

数据解析

二、基于文件的source和sink

如果要从文本文件中读取数据,可以直接env.readTextFile(path)

就可以以文本的形式读取该文件中的内容。当然也可以使用env.readFile(fileInputFormat, path)根据指定的fileInputFormat格式读取文件中的内容。

如果数据在FLink内进行了一系列的计算,想把结果写出到文件里,也可以直接使用内部预定义的一些sink,比如将结果已文本或csv格式写出到文件中,可以使用DataStream的writeAsText(path)和 writeAsCsv(path)。

三、基于Socket的Source和Sink

提供Socket的host name及port,可以直接用StreamExecutionEnvironment预定的接口socketTextStream创建基于Socket的source,从该socket中以文本的形式读取数据。当然如果想把结果写出到另外一个Socket,也可以直接调用DataStream writeToSocket。

基于内存 Collections、Iterators 的Source可以直接基于内存中的集合或者迭代器,调用StreamExecutionEnvironment fromCollection、fromElements构建相应的source。结果数据也可以直接print、printToError的方式写出到标准输出或标准错误。

三步简单实现

1.Split/Splitstate

Split:外部系统分片、Division of external systern

SptitSerializer:序列化/反序列化 Split传递给SourceReaderSerialize/Deserialize split to deliver to SourceReader

SplitState: Split状态,用于Checkpoint与恢复State of split for checkpointing and recovering

2.SplitEnumerator

发现与订阅Split
Discaver and subscribe ta splits

EnurnState: Enumerator的状态,用于Checkpoint与恢白state of enumerator for checkpointing and recovering

EnumStateSerializer:序列化/反序列化EnurnStateSerialize/Deserialize EnumState

3.SourceReader

SplitReader:与外部系统进行数据交互的接口lnterface for data interacting with external system

retcherManager:选择线程模型
choosing thread model

RecordEmitter:转换消息类型与处理事件时间Converting record type and dealing with event tirme

二阶段提交

提交执行阶段Commit Phase

协调者决定提交

执行者执行提交

执行者回复结果

如果在提交阶段出现错误,则会出现以下情况:

代码示例:

public class KafkaSinkDemo {

    public static void main(String[] args) {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 自定义SourceFunction

        CustomSourceFunction sourceFunction = new CustomSourceFunction();

        // 添加数据源

        DataStreamSource<Tuple2<String, Long>> customDS = env.addSource(sourceFunction);

        // 处理,转为String

        DataStream<String> resultDS = customDS.map(new MapFunction<Tuple2<String, Long>, String>() {

            @Override

            public String map(Tuple2<String, Long> value) throws Exception {

                return value.f0 + "|" + value.f1;

            }

        });

        // 创建FlinkKafkaProduce

        FlinkKafkaProducer011<String> kafkaProducer011 = generateKafkaProducer();

        // 发入Kafka

        resultDS.addSink(kafkaProducer011);

        try {

            env.execute();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

    /**

     * 生成 FlinkKafkaProducer

     */

    private static FlinkKafkaProducer011<String> generateKafkaProducer() {

        // 创建FlinkKafkaProducer

        FlinkKafkaProducer011<String> kafkaProducer011 = new FlinkKafkaProducer011<>(

                "192.168.0.101:9092", "topic_01", new SimpleStringSchema()

        );

        // 自定义序列化 - 示例

        /*

        new KeyedSerializationSchema<String>() {

            @Override

            public byte[] serializeKey(String element) {

                // 可以直接为null,也可以为String编码

                return null;

            }

            @Override

            public byte[] serializeValue(String element) {

                // 编码String为byte[]

                return element.getBytes(StandardCharsets.UTF_8);

            }

            @Override

            public String getTargetTopic(String element) {

                // 由源码可知,此处优先级最高,FlinkKafkaProducer011中传的topicid是默认值

                // 可以在此决定不同的数据发往到不同的topic

                return null;

            }

        };

        */

        return kafkaProducer011;

    }

}

提交执行阶段

代码示例:

public class RedisSinkDemo {

    public static void main(String[] args) {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 自定义SourceFunction

        CustomSourceFunction sourceFunction = new CustomSourceFunction();

        // 添加数据源

        DataStreamSource<Tuple2<String, Long>> customDS = env.addSource(sourceFunction);

        // 创建RedisSink

        RedisSink<Tuple2<String, Long>> redisSink = generateRedisSink();

        // 发入Redis

        customDS.addSink(redisSink);

        try {

            env.execute();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

    /**

     * 生成RedisSink

     */

    private static RedisSink<Tuple2<String, Long>> generateRedisSink() {

        // Redis配置

        FlinkJedisPoolConfig config = new FlinkJedisPoolConfig.Builder()

                .setMaxTotal(8) // 最大实例总数

                .setMaxIdle(4) // 实例最多空闲数

                .setMinIdle(2)

                .setHost("192.168.0.110")

                .setPort(6379)

                .build();

        // Mapper

        RedisMapper<Tuple2<String, Long>> redisMapper = new RedisMapper<Tuple2<String, Long>>() {

            @Override

            public RedisCommandDescription getCommandDescription() {

                // 定义保存数据到Redis的命令

                return new RedisCommandDescription(

                        RedisCommand.HSET, // 使用hset命令

                        "my_hash" // 表名

                );

            }

            @Override

            public String getKeyFromData(Tuple2<String, Long> tuple2) {

                return tuple2.f0;

            }

            @Override

            public String getValueFromData(Tuple2<String, Long> tuple2) {

                return tuple2.f1.toString();

            }

        };

        return new RedisSink<>(config, redisMapper);

    }

}

Sink模型

FLIP-27 Refactor Source Interface:
https://cwiki.apache.org/confluence/display/FLINK/FLIP-27%3A+Refactor+Source+Interface.FLIP-143

Unified Sink APl
https://cwiki.apache.org/confluence/display/FLINK/FLIP-143%3A+Unified+Sink+API.

An Overview of End-to-End Exactly-Once Processing in Apache Flink
https://flink.apache.org/features/2018/03/01/end-to-end-exactly-once-apache-flink.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值