本文主要参考自:
- Apache Flink 漫谈
Apache Flink 漫谈系列 - 序
Apache Flink 漫谈系列 - 概述
Apache Flink 漫谈系列 - Watermark
Apache Flink 漫谈系列 - State
Apache Flink 漫谈系列 - Fault Tolerance
Apache Flink 漫谈系列 - 流表对偶(duality)性
Apache Flink 漫谈系列 - 持续查询(Continuous Queries)
Apache Flink 漫谈系列 - SQL概览
Apache Flink 漫谈系列 - JOIN 算子
双流join 根据on字段进行shuffle
Apache Flink 漫谈系列 - JOIN LATERAL
Apache Flink 漫谈系列- Temporal Table JOIN
Apache Flink 漫谈系列 - Time Interval(Time-windowed) JOIN
Apache Flink 漫谈系列 - Table API 概述
Apache Flink 漫谈系列 - DataStream Connectors之Kafka - https://enjoyment.cool/?spm=ata.13261165.0.0.31933b32FibhNh
- Flink专栏
问题
- 同一个分区可以有多个运行线程?
1. 概述
- flink是java编写的
- Flink 是一个针对流数据和批数据的分布式处理引擎,批处理只是流处理的一个特例。
- 如果要对比 Flink 和 Spark 的话,Flink 并没有将内存完全交给应用层。这也是为什么 Spark 相对于 Flink,更容易出现 OOM 的原因(out of memory)。就框架本身与应用场景来说,Flink 更相似与 Storm。
- 首先有了流表对偶性,流表转换才有理论支撑,流表才能互转;有了动态表的概念,SQL才能作用于流上,才能进行持续查询;有了持续查询的概念,才能真正解释流式SQL计算永不结束,结果永远在更新;增量计算配合查询和引擎优化,才有了Blink的高性能;Early Emit和Retraction,一个使用Blink有了无限逼近秒级毫秒级的低延时,一个保证了流式语义的正确性。
2. Streaming Dataflow
- Flink 程序是由 Stream 和 Transformation 这两个基本构建块组成,其中 Stream 是一个中间结果数据,而Transformation 是一个操作,它对一个或多个输入 Stream 进行计算处理,输出一个或多个结果 Stream。
- Flink 程序被执行的时候,它会被映射为 Streaming Dataflow。一个 Streaming Dataflow 是由一组 Stream 和Transformation Operator 组成,它类似于一个 DAG 图,在启动的时候从一个或多个 Source Operator 开始,结束于一个或多个 Sink Operator。
- Streaming Dataflow的并行运行视图
一个 Stream 可以被分成多个 Stream 分区(Stream Partitions),一个 Operator 可以被分成多个 Operator Subtask,每一个 Operator Subtask 是在不同的线程中独立执行的。一个 Operator 的并行度,等于 Operator Subtask 的个数,一个 Stream 的并行度总是等于生成它的 Operator 的并行度。
3. 架构
Program Code:我们编写的 Flink 应用程序代码
-
Job Client:Job Client 不是 Flink 程序执行的内部部分,但它是任务执行的起点。 Job Client 负责接受用户的程序代码,然后创建数据流,将数据流提交给 Job Manager 以便进一步执行。 执行完成后,Job Client 将结果返回给用户
Job Manager:主进程(也称为作业管理器)协调和管理程序的执行。 它的主要职责包括安排任务,管理checkpoint ,故障恢复等。机器集群中至少要有一个 master,master 负责调度 task,协调 checkpoints 和容灾,高可用设置的话可以有多个 master,但要保证一个是 leader, 其他是 standby; Job Manager 包含 Actor system、Scheduler、Check pointing 三个重要的组件。
Task Manager:从 Job Manager 处接收需要部署的 Task。Task Manager 是在 JVM 中的一个或多个线程中执行任务的工作节点。 任务执行的并行性由每个 Task Manager 上可用的任务槽决定。 每个任务代表分配给任务槽的一组资源。 例如,如果 Task Manager 有四个插槽,那么它将为每个插槽分配 25% 的内存。 可以在任务槽中运行一个或多个线程。 同一插槽中的线程共享相同的 JVM。 同一 JVM 中的任务共享 TCP 连接和心跳消息。Task Manager 的一个 Slot 代表一个可用线程,该线程具有固定的内存,注意 Slot 只对内存隔离,没有对 CPU 隔离。默认情况下,Flink 允许子任务共享 Slot,即使它们是不同 task 的 subtask,只要它们来自相同的 job。这种共享可以有更好的资源利用率。
3.1 parallelism和slot
- 设置parallelism的几种方式
算子设置并行度 > env 设置并行度 > 配置文件默认并行度
- 配置文件:
flink-conf.yaml
- 运行时设定,例如:
./bin/flink run -p 10 ../word-count.jar
- 程序全局设定:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(10);
- 为每个算子设定
data.keyBy(new xxxKey())
.flatMap(new XxxFlatMapFunction()).setParallelism(5)
.map(new XxxMapFunction).setParallelism(5)
.addSink(new XxxSink()).setParallelism(1)
- slot
slot 的数量通常与每个 TaskManager 的可用 CPU 内核数成比例。一般情况下你的 slot 数是你每个 TaskManager 的 cpu 的核数。
每个 Flink TaskManager 在集群中提供 slot。 slot 的数量通常与每个 TaskManager 的可用 CPU 内核数成比例。一般情况下你的 slot 数是你每个 TaskManager 的 cpu 的核数。
parallelism:算子设置并行度 > env 设置并行度 > 配置文件默认并行度
slot:
如果 Task Manager 有四个 slot,那么它将为每个 slot 分配 25% 的内存。 可以在一个 slot 中运行一个或多个线程。 同一 slot 中的线程共享相同的 JVM。 同一 JVM 中的任务共享 TCP 连接和心跳消息。Task Manager 的一个 Slot 代表一个可用线程,该线程具有固定的内存,注意 Slot 只对内存隔离,没有对 CPU 隔离。默认情况下,Flink 允许子任务共享 Slot,即使它们是不同 task 的 subtask,只要它们来自相同的 job。这种共享可以有更好的资源利用率。
为什么stream processing takes on everything。
大部分数据的产生过程都是随时间生成的流,比如一个 Petabyte 的数据不会凭空产生。这些数据通常都是一些事件的积累,比如支付、将商品放入购物车,网页浏览,传感器采样输出,基于数据是流的想法,我们对数据处理可以有相应的理解。比如将过去的历史数据看做是一个截止到某一时刻的有限的流,或是将一个实时处理应用看成是从某一个时刻开始处理未来到达的数据。可能在未来某个时刻它会停止,那么它就变成了处理从开始时刻到停止时刻的有限数据的批处理。当然,它也有可能一直运行下去,不断处理新到达的数据。这个对数据的重要理解方式非常强大,基于这一理解,Flink 可以支持整个数据处理范畴内的所有场景。
4. 几种Time和水印
- Processing Time
Processing Time 是指事件被处理时机器的系统时间。Processing Time 是最简单的 “Time” 概念,不需要流和机器之间的协调,它提供了最好的性能和最低的延迟。但是,在分布式和异步的环境下,Processing Time 不能提供确定性,因为它容易受到事件到达系统的速度(例如从消息队列)、事件在系统内操作流动的速度以及中断的影响。自动生成watermark。 - Event Time
Event Time 是事件发生的时间,一般就是数据本身携带的时间。这个时间通常是在事件到达 Flink 之前就确定的,并且可以从每个事件中获取到事件时间戳。在 Event Time 中,时间取决于数据,而跟其他没什么关系。Event Time 程序必须指定如何生成 Event Time 水印,这是表示 Event Time 进度的机制。
需要定义WaterMark - Ingestion Time
Ingestion Time 是事件进入 Flink 的时间。 在源操作处,每个事件将源的当前时间作为时间戳,并且基于时间的操作(如时间窗口)会利用这个时间戳。自动生成watermark。
Ingestion Time 在概念上位于 Event Time 和 Processing Time 之间。 与 Processing Time 相比,它稍微贵一些,但结果更可预测。因为 Ingestion Time 使用稳定的时间戳(在源处分配一次),所以对事件的不同窗口操作将引用相同的时间戳,而在 Processing Time 中,每个窗口操作符可以将事件分配给不同的窗口(基于机器系统时间和到达延迟)。
与 Event Time 相比,Ingestion Time 程序无法处理任何无序事件或延迟数据,但程序不必指定如何生成水印。在 Flink 中,,Ingestion Time 与 Event Time 非常相似,但 Ingestion Time 具有自动分配时间戳和自动生成水印功能。
- watermark
一些操作符消耗多个输入流; 例如,一个 union,或者跟随 keyBy(…)或 partition(…)函数的运算符。 操作符当前事件时间是其输入流的事件时间的最小值。
5. window
data.keyBy(1)
.timeWindow(Time.minutes(1)) //tumbling time window 每分钟统计一次数量和
.sum(1);
data.keyBy(1)
.timeWindow(Time.minutes(1), Time.seconds(30)) //sliding time window 每隔 30s 统计过去一分钟的数量和
.sum(1);
data.keyBy(1)
.countWindow(100) //统计每 100 个元素的数量之和
.sum(1);
data.keyBy(1)
.countWindow(100, 10) //每 10 个元素统计过去 100 个元素的数量之和
.sum(1);
5.1 窗口分类
5.1.1 GroupBy Window
GroupBy window的创建是数据驱动的,也就是说,窗口是在属于此窗口的第一个元素到达时创建。当 窗口结束 时候删除窗口及状态数据。那么窗口何时结束?如上面描述的Blink Window的Unbounded和Bounded的属性,对于Unbounded的窗口,窗口从第一条数据到来,窗口一直存在(窗口中数据和状态会有一定的策略进行清理),窗口伴随整个Stream。对于Bounded窗口,以基于时间的Tumble窗口为例,在属于此窗口的第一个元素到达时创建,在达到窗口的结束时间戳位置窗口被删除,即,00:00:00 ~ 00:02:00的一个2分钟的tumble窗口,窗口会在[00:00:00 ~ 00:02:00)间的第一个元素到来时候创建,在00:02:00的时间戳时候删除该窗口.
- Global - 全局窗口,所有数据在一个窗口中,对应上面SQL定义中无 [WINDOW(definition)]情况;
每到一条数据都会触发一次计算。Unbounded,无时间属性。 - Tumble - 滚动窗口,窗口数据有固定的大小,窗口数据无叠加;
datastream api支持count和time,但是sql只支持time。 - Hop - 滑动窗口,窗口数据有固定大小,并且有固定的窗口重建频率,窗口数据有叠加;
datastream api支持count和time,但是sql只支持time。 - Session - 会话窗口,窗口数据没有固定的大小,根据窗口数据活跃程度划分窗口,窗口数据无叠加;
只支持时间。
5.1.2 OVER Window
- ROWS OVER Window - 每一行元素都视为新的计算行,即,每一行都是一个新的窗口;
SELECT
agg1(col1) OVER(
[PARTITION BY (value_expression1,..., value_expressionN)]
ORDER BY timeCol
ROWS
BETWEEN (UNBOUNDED | rowCount) PRECEDING AND CURRENT ROW) AS colName,
...
FROM Tab1
- RANGE OVER Window - 具有相同时间值的所有元素行,视为同一计算行,即,具有相同时间值的所有行都是同一个窗口;
SELECT
agg1(col1) OVER(
[PARTITION BY (value_expression1,..., value_expressionN)]
ORDER BY timeCol
RANGE
BETWEEN (UNBOUNDED | timeInterval) PRECEDING AND CURRENT ROW) AS colName,
...
FROM Tab1
5.2. 窗口机制
6. 持续查询和增量计算
我们进行查询大多数场景是进行数据聚合,比如查询SQL中利用count,sum等aggregate function进行聚合统计,那么流上的数据源源不断的流入,我们既不能等所有事件流入结束(永远不会结束)再计算,也不会每次来一条事件就像传统数据库一样将全部事件集合重新整体计算一次,在持续查询的计算过程中,Blink采用增量计算的方式,也就是每次计算都会将计算结果存储到state中,下一条事件到来的时候利用上次计算的结果和当前的事件进行聚合计算。
Blink以为事件打标产生delete事件(该方式叫做Retract)的方式保证语义的正确性,完美的支持流上的持续查询。
对于无限流JOIN来说,一直等到state TTL。 默认TTL是1.5 天。待确认!!!!!
持续查询中的Blink Sink语义:
在Blink上面可以根据实际外部存储的特点(是否支持PK)来在Blink内部的DDL中为Sink进行PK的定义。Blink中用户可感知的有两种:
- Append 模式 - 该模式用户在定义Sink的DDL时候不定义PK,在blink内部生成的所有只有INSERT语句
- Upsert 模式 - 该模式用户在定义Sink的DDL时候可以定义PK,在blink内部会根据事件打标(retract机制)生成INSERT/UPDATE和DELET 语句,其中如果定义了PK, UPDATE语句按PK进行更新,如果没有定义PK UPDATE会按整行更新
7. backpressure
参考Flink 原理与实现:如何处理反压问题
流处理系统需要能优雅地处理反压(backpressure)问题。反压通常产生于这样的场景:短时负载高峰导致系统接收数据的速率远高于它处理数据的速率。许多日常问题都会导致反压,例如,垃圾回收停顿可能会导致流入的数据快速堆积,或者遇到大促或秒杀活动导致流量陡增。反压如果不能得到正确的处理,可能会导致资源耗尽甚至系统崩溃。
- storm的处理
Storm 是通过监控 Bolt 中的接收队列负载情况,如果超过高水位值就会将反压信息写到 Zookeeper ,Zookeeper 上的 watch 会通知该拓扑的所有 Worker 都进入反压状态,最后 Spout 停止发送 tuple。具体实现可以看这个 JIRA STORM-886。 - jstorm的处理
JStorm 认为直接停止 Spout 的发送太过暴力,存在大量问题。当下游出现阻塞时,上游停止发送,下游消除阻塞后,上游又开闸放水,过了一会儿,下游又阻塞,上游又限流,如此反复,整个数据流会一直处在一个颠簸状态。所以 JStorm 是通过逐级降速来进行反压的,效果会较 Storm 更为稳定,但算法也更复杂。另外 JStorm 没有引入 Zookeeper 而是通过 TopologyMaster 来协调拓扑进入反压状态,这降低了 Zookeeper 的负载。 - flink的处理
在 Flink 中,这些分布式阻塞队列就是这些逻辑流,而队列容量是通过缓冲池(LocalBufferPool)来实现的。每个被生产和被消费的流都会被分配一个缓冲池。缓冲池管理着一组缓冲(Buffer),缓冲在被消费后可以被回收循环利用。这很好理解:你从池子中拿走一个缓冲,填上数据,在数据消费完之后,又把缓冲还给池子,之后你可以再次使用它。