Flink
数据处理架构
批处理
-
事务性处理
- 多应用共享一个数据库,数据库支持事务,一般都是单体
- 更改数据库表模式和数据库扩缩容时费劲
-
分析型处理
- 通过ETL把数据导到数据仓库
- 使用定期批处理任务生成报告或者即席查询分析数据
流处理
- 有状态,支持存储中间结果
- 支持事件型应用,事件流可能是一个无限的事件序列
基础概念
并行
-
数据并行
- 同一个算子的不同实例处理不同的数据,既算子的并行度
-
任务并行
- 不同算子本来是有先后顺序的,放在不同的机器上,实现流水线式的作业
数据交换策略
-
算子间均有多个实例,那两种算子多对多实例间怎么传递数据
-
转发
-
基于键值
-
随机
-
广播
- 前序的同一个数据会被后续的多个实例处理
-
-
api
-
shuffle
- 随机,每个发送任何和所有下游任务都有通信
-
rebalance
- 轮流,所有发送任务和接收任务之间都有通信
-
rescale
- 轮流,每个发送任务只和部分下游任务有通信,更加轻量
-
global
- 将当前算子所有任务发往下游的第一个任务,既归拢
-
broadcast
- 广播,每个事件复制发送给下游所有任务
-
性能指标
-
分类
-
延迟
-
一般定义为从接收时间到输出时间的间隔时间,不纠结内部各个算子的延迟,因为并行度等问题,不好描述
- Flink内部通过一个自己生成特殊的延迟标记来模拟事件,实现监测
-
-
吞吐
- 系统每单位事件可以处理的数据量
-
-
关系
-
延迟越低,吞吐肯定越高
-
吞吐高延迟不一定低,因为可能有网络IO之类的高延迟环节
-
如果接收数据的速度大于吞吐,则会导致耗尽缓冲区,甚至可能丢失数据,这个叫做背压
-
跨机器的传输采用TCP,每个任务都有自己收发数据的缓冲区,跟TCP链接一对一,所以在shffle和广播的交换策略下,缓冲区的数量可达到算子任务数的平方个
-
flink控制背压的方式是通过缓冲区和信用值,信用值既接收端沟通发送端的积压量信息,然后调节各个发送端下一轮的发送量,从而实现动态流量控制
- 子主题 1
-
操作
-
转换
-
概念
- 每个事件只处理一次
-
api
-
作用于单个事件的基本转换,输入和输出都是dataStream
-
map
- 单个事件的1对1
-
flatmap
- 单个事件的1转n
-
fliter
- 单个事件的1转0、1
-
-
按key分的转换,dataSteam和keyedstream之间的互转
-
keyby
- 逻辑上将单条流转成多条,注意只是逻辑上,靠的是通过多个keyed的算子状态来区分,但是物流上不论任务的缓冲区还是检查点什么的,都是一条流
- keyby的方法很多,除了可以指定tuple、还可以直接用"."来获取结构体中的内容,还可以通过通配符来解析多个字段。
-
滚动聚合的那些api
- 将keyedStream中每个逻辑子流都聚合成一个单值,然后所有的逻辑子流单值连在一起就变成了一个dataStream
-
-
单条流和多条流之间的转换
-
union
-
物理上多条相同泛型的datastream合并成一条
- 没有顺序保证,也不会去重
-
-
connect
-
物理上多条不同泛型的datastream流合并成一条
- 合并后的流还是针对每种类型的事件分别处理,唯一的好处是可以同时获得两条流的上下文信息,可以存储一些状态来做判断
-
-
split和select
- union的逆操作
-
sideout
- union的逆操作
-
-
-
实现函数
- 所有的实现函数必须都是可序列化的,因为Flink会使用java的序列化机制将函数发到对应的工作进程
- 所有的函数都有对应的rich版本,就是有open和close方法
- 所有的函数都有对应的底层版本,既process版本,可以获取对应的context和计时器
-
-
滚动聚合
-
概念
- 合并事件和当前状态生成一个新的状态
- 必须满足交换律和结合律,否则就对事件顺序有要求
- 滚动聚合是将一个dataStream转换为一个单值
-
api
-
系统自带
-
sum
-
min
-
max
-
minby
- min是求最小值,minby是求最小值所对应的事件
-
maxby
-
-
reduce
- 输入参数和状态类型一致
-
aggregate
- 输入参数和状态类型可以不同,比reduce更灵活
-
-
-
窗口
-
概念
- 收集并缓存多个事件来生成一个结果
- keyed流上的窗口可以并行处理,但是最多也就是每个key一个线程,不能再分了
- 非keyed的流只能单线程处理
-
组件
-
窗口分配器
-
分类
-
滚动窗口
- 固定收集时间或者收集数量
-
会话窗口
- 固定间隔时间,不能根据数量
-
滑动窗口
- 维度可以是时间也可以是数量
- 有收集长度、滑动偏移量两个参数
-
-
api
-
keyedStream
- window()
-
非keyedStream
- windowAll()
-
-
-
窗口函数
-
全量聚合函数,会保存所有元素
- processWindowFunction
-
增量聚合函数,只保存聚合结果
-
reduceFunction
- 输入和输出类型一致
-
aggregateFunction
- 输入类型、中间类型、输出类型均可不同
-
-
-
-
代码
- getExecutionEnvironment可以根据上下文环境的不同,获取本地或者远程执行环境
- 只有在env.execute之后,系统才会触发程序执行
时间语义
-
事件时间
-
在靠近source的地方手动生成,否则在keyby等会导致乱序的算子后面生成的话,时间戳就混乱了
-
水位线的生成
-
每个任务的水位线由所有前置输入分区中最小的一个水位线决定
- 如果有一个分区的水位线不涨了,则整个应用都停了
-
水位线的传播永远都是广播形式
-
-
迟到事件的处理
-
可以设置延迟容忍度
- 延迟容忍度内的迟到数据,每次来一个都会调用一次处理函数,超过了则直接丢弃
-
可以sideOut迟到数据
-
-
-
-
处理时间
- 既机器时间
-
ingestion time 摄入时间
- 相当于事件时间和处理时间的混合体,很少用
状态管理
-
作用域
-
算子状态
-
概念
- 作用于某个算子任务,不能被别的任务访问,无论该任务是否是来自同一个算子(注意广播状态也是访问的当前任务的算子状态,只是大家的状态是相同的值罢了)
- 但是要注意,在并行度发生变化的时候,可能同一个算子的多个任务状态需要重新洗牌,这个时候就可能会需要把多个任务的算子状态发给同一个算子,不过这个过程对开发人员不感知
-
三类原语
- 列表状态
- 联合列表状态
- 广播状态
-
-
键值分区状态
-
概念
- 一个任务中可能维护了多个键的分区,每个键的分区都有自己的状态实例
-
三类原语
- 单值状态
- 列表状态
- map状态
- reducingState
- aggregatingState
-
-
-
结果保障
-
至少一次
-
至多一次
-
精确一次
-
端到端精确一次
-
source要求有重置读取能力
-
sink要求有幂等写或者事务写的能力
-
幂等写
- 重放过程中,可能只有一部分状态被重新覆盖,导致有一些先前写入的但是不再需要的状态残留,所以可能会有整体结果不一致的可能
-
事务写
-
最终结果一定是一致的,但是在提交前,结果对外不可见,所以延迟会高一些
-
WAL写前日志
- 由flink提供,但是无法保证百分百精确一次,且有数据写入的波峰
-
2PC
- 需要sink本身支持,可以百分百精确一致,且没有数据写入波峰
-
-
-
-
-
-
状态恢复
-
检查点
- 检查点的分隔符总是以广播形式发送
- 每个任务在收接收分隔符时,会有一个分隔符对齐的过程,保证所有前置输入分区的检查点一致
- 检查点是自动触发,但是不会自动保存
-
保存点
- 保存点是手动触发,但是会持久保存
-
-
状态存储
-
状态后端
- 内存
- 文件
- rocksDb
-
状态获取
- 状态不是存在处理函数里,而是在context里,处理函数通过context获取对应实例的状态
- 每个状态原语都有自己的状态描述符,里面包含了状态名称和类型,比如valueStateDescriptor
-
运行架构
数据流处理
-
flink
-
jobManager
- 一个作业一个,主要负责协调
-
taskManager
-
一个taskManager是一个进程,其中有多个slot,但每个slot不一定只有一个线程,slot是任务的概念,不是计算资源的概念
- 一个TaskManager默认的slot数会等于机器CPU的线程数
-
每个slot允许执行一个任务,一个算子的所有slot数既其并行度(taskManager数乘以每个taskManager上的slot数)
-
一个任务可以是一个算子实例,也可以是因为任务链接连在一起的多个连续算子实例
- 任务链接的前提条件是需要相邻的算子有相同的并行度
-
一个taskManager中的任务可以是来自一个作业,也可以是来自不同作业
-
-
dispatcher
- 多个作业共享,主要负责协调jobManager和clinet
-
集群管理
- Yarn等
持久化
- hdfs等
高可用
- zookeeper