核心目标:数据流上的有状态计算
Flink是一个框架和分布式处理引擎,用于对无界和有界数据进行有状态计算
Flink | Streaming | |
计算模型 | 流计算 | 微批处理 |
时间语义 | 事件时间、处理时间 | 处理时间 |
窗口 | 多、灵活 | 少、不灵活 |
状态 | 有 | 无 |
流式SQL | 有 | 无 |
Dataset批处理已过时,使用Datastream流处理
Datastream最后执行env.execute() 类似于sparkstream的ssc.start()
集群角色
客户端FlinkClient:代码由客户端获取并转换,提交给JobManger。
JobManger:Flink集群的管事人,对作业中央调度管理;进一步处理转换分发给TaskManager。
TaskManager:干活的人,做数据的处理操作
修改conf -- 1 flink-conf.yaml 2.works 3.masters(jobmanager) 分发,修改其他机器的taskmanager.host
bin/start-cluster.sh启动flink集群
默认端口号 8081
部署模式:会话模式、单作业模式 都是在客户端执行
应用模式 是直接提交到JobManager上运行,每提交一个应用单独启动一个JobManager,也就是创建一个集群。执行结束后JobManager也关闭了。
运行模式 (集群怎么搭建的)Yarn运行模式:客户端把Flink应用提交给Yarn的ResourceManager,Yarn的ResourceManager会向Yarn的NodeManager申请容器,在这些容器上,Flink会部署JobManager和TaskManager的实例,从而启动集群。Flink会根据运行在JobManager上的作业所需要的Slot数量动态分配TaskManager资源。
历史服务器 端口号:8082
运行时架构-Standlone会话模式为例
核心概念
并行度:一个特定的算子子任务的个数称为并行度。
设置并行度:1.算子后.setParallelism(2) 2.env.setParallelism(3)全局指定 .都不指定配置文件有默认并行度配置
算子链:上下游算子的合并。
算子之间的传输关系是一对一和重分区
算子串在一起的条件是一对一(分发规则是forward)和并行度相同。
为什么要合并:节省资源,
算子链的api:1.全局禁用算子链env.disableOperatorChaining()
2.某个算子不参与链化 A.disableChaining()
3.从某个算子开启新链条 算子A.startNewChain()
任务槽:
设置taskmanager的slot数量 配置文件中 taskmanager.numberOfTaskSlots
任务槽的共享组,在同一个作业中,不同任务节点的并行子任务,就可以放到同一个slot上执行。
slot是静态的,并行度是动态的,slot<并行度就不会运行
Yarn应用模式作业提交流程
作业提交给Yarn的ResourceManager,选择一个节点NodeManager启动一个容器,容器里运行ApplicationMaster(JobManager),JobManager里面启动分发器和资源管理器,分发器启动JobMaster生成逻辑流图StreamGraph,经过算子链的优化生成作业流图JobGraph,将作业流图并行化展开生成执行流图ExecutionGraph。向资源管理器注册请求slot,资源管理器向Yarn的ResouceManager申请资源,在NodeManager里创建容器存放TaskManager并向flink的大管家启动成功资源注册slot,分配一下slot,分配任务生成物理流图PhysicalGraph。
DataStream API
获取执行环境environment-读取数据源source-转换操作transformation-输出sink-执行execute
源算子source
从集合读取数据
从文件读取数据 fromsource
从kafka读数据 kafka Consumer 消费原则:
一个Topic中的一个Partition只能被一个消费者组中的一个消费者消费,
一个消费者组中的一个消费者可以消费一个Topic中的多个Partition。
最好的情况是消费者个数=Topic的partition
转换算子transformation
map:一进一出
filter:过滤
flapmap:一进多出
聚合算子:
sum:
main:
max:只会取比较字段的最大值,非比较字段保留第一次的值
maxby:取比较字段的最大值,同时非比较字段取最大值的这条数据的值
min
minby
reduce 思想:两两聚合。从第二个元素开始聚合输入类型=输出类型 类型不能变
富函数:
分区算子:keyby:对数据重分区,不能设置并行度。一个子任务理解为一个分区。一个分区可以存在多个组
输出算子:
处理函数
process processfunction,拥有富含数的功能,生命周期方法和状态编程。有处理数据的方法,processElement。有onTimer方法,该方法用于定时器TimerService编程。有上下文对象Context ,可以获取定时器编程也可以侧输出流。
分流
侧输出流
合流
联合 union 流的类型必须一致,一次可以合并多条流
连接 connect
双流联结 join
时间语义:
事件时间:一个数据产生的时间(时间戳)
处理时间:数据真正被处理的时间、系统时间。
水位线(watermake):衡量事件时间进展的标记。主要内容就是一个时间戳用来指示当前的事件时间
水位线不断增长
水位线配合窗口一起使用完成对乱序数据的正确处理。
水位线是流处理中对低延迟和结果正确性的一个权衡机制。
watermake+等待时间能够处理延迟数据。
水位线传递原则:
上游Task向下游 广播水位线。
下游Task收到上游多个Task的水印,取最小的作为当前水印。
迟到数据处理:
1.watermake等待时间,设置一个不算特别大的,一般是秒级,在乱序和延迟 取舍
2.设置一定的窗口允许迟到,只考虑大部分的迟到数据,极端小部分迟到很久的数据不管
3.极端小部分迟到很久的数据,找到侧输出流,获取到之后可以做各种处理。
窗口
划定一个数据的范围
1.什么时候触发,输出? 时间进展>=窗口的最大时间戳
2.窗口是怎么划分的? start=向下取整,取窗口长度的整数倍
end=start+窗口长度
窗口左闭右开
窗口的分类 滑动、滚动、会话、全局窗口
窗口的使用:1.明确窗口分配器(时间窗口,计数窗口),2.明确窗口算子
增量聚合函数解决聚合过程,全窗口函数聚合确认窗口信息,来一条计算一条。
ReduceFunction输入和输出类型一致
AggregateFunction输入和输出类型可以不一致,里面有个累加器
全窗口函数:与增量聚合函数不同,全窗口函数需要先收集窗口中的数据,并在内部缓存起来,等到窗口要输出结果的时候再取出数据进行计算。可以获取窗口的信息(窗口的开始结束时间)
窗口联结Window Join
间隔联结
Flink的状态: 托管状态Managed State,(Flink统一管理的)状态的存储访问、故障恢复和重组等一系列问题都由Flink实现;原始状态 Raw State。
算子状态:作用范围是算子,算子的多个并行实例各自维护一个状态
列表状态
联合列表状态
广播状态:广播配置。
按键分区状态:每个分组维护一个状态 首先经过keyby
值状态:单值,只能维护一个值
状态后端:管理本地状态的存储方式和位置 本地状态存哪里
默认哈希表状态后端HashMapStateBackend,(把状态存放在TaskManager的JVM堆内存中,窗口中收集的数据和触发器以键值对的形式存储,底层是一个哈希表,读写快,存不了太多,受TaskManager内存的限制)内嵌RocksDB状态后端(持久化到本地硬盘,状态被序列化成字节数组,读写慢,但可以存很大的状态)
检查点:故障后的恢复。存档读档的思路,将之前某个时间点所有的状态保存下来,这份存档就是所谓的检查点。 外部存储。
周期性的触发我们应该在所有任务(算子)都恰好处理完一个相同的输入数据的时候,将它们的状态保存下来。
基于Chandy-Lamport算法的分布式快照,可以在不暂停整体流处理的前提下,将状态备份保存到检查点。
当上游任务向多个并行下游任务发送barrier时,需要广播出去;
而当多个上游任务向同一个下游任务传递分界线时,需要在下游任务执行“分界线对齐”操作,也就是需要等到所有并行分区的barrier都到齐,才可以开始状态的保存。
检查点算法的总结:
1、Barrier对齐:一个Task收到所有上游同一个编号的barrier之后,才会对自己的本地状态做备份
精准一次:在barrier对齐过程中,barrier后面的数据阻塞等待(不会越过barrier)
至少一次:在barrier对齐过程中,先到的barrier后面的数据不阻塞接着计算
2、非Barrier对齐:一个Task收到第一个barrier时,就开始备份,能保证精准一次
先到的barrier将本地状态备份,后面的数据接着计算,
未到的barrier其前面的数据接着输出,同时也保存到备份中
最后一个barrier到达该Task时,这个Task的备份结束。
保存点:由用户明确地手动触发保存操作,所以就是“手动存盘”。
状态一致性:一致性就是结果的正确性,一般从数据丢失、数据重复评估。
最多一次,至少一次,精准一次
端到端的状态一致性:需要考虑flink内部,数据源,外部存储。不能拖后腿
端到端精确一次:最希望做到,也是最难做到的
输入端保证:kafka可以保证,可以重置读取数据偏移量。
Flink端保证:开启检查点且精准一次。
输出端保证:幂等 利用mysql的主键upsert,hbase的rowkey唯一,redis的set
事务 :预写日志: 有应答机制,没应答会重复写入 至少一次
两阶段提交:分布式事务的提交 ,先做预提交,等检查点完成之后再做正式提交。
不过两阶段提交虽然精巧,却对外部系统有很高的要求。这里将2PC对外部系统的要求列举如下:
在具体应用中,实现真正的端到端exactly-once,还需要有一些额外的配置:
(1)必须启用检查点
(2)指定KafkaSink的发送级别为DeliveryGuarantee.EXACTLY_ONCE
(3)配置Kafka读取数据的消费者的隔离级别
这里所说的Kafka,是写入的外部系统。预提交阶段数据已经写入,只是被标记为“未提交”(uncommitted),而Kafka中默认的隔离级别isolation.level是read_uncommitted,也就是可以读取未提交的数据。这样一来,外部应用就可以直接消费未提交的数据,对于事务性的保证就失效了。所以应该将隔离级别配置
为read_committed,表示消费者遇到未提交的消息时,会停止从分区中消费数据,直到消息被标记为已提交才会再次恢复消费。当然,这样做的话,外部应用消费数据就会有显著的延迟。
(4)事务超时配置
Flink的Kafka连接器中配置的事务超时时间transaction.timeout.ms默认是1小时,而Kafka集群配置的事务最大超时时间transaction.max.timeout.ms默认是15分钟。所以在检查点保存时间很长时,有可能出现Kafka已经认为事务超时了,丢弃了预提交的数据;而Sink任务认为还可以继续等待。如果接下来检查点保存成功,发生故障后回滚到这个检查点的状态,这部分数据就被真正丢掉了。所以这两个超时时间,前者应该小于等于后者。