Flink

flink与spark的差异

最显著的区别就是flink是标准的实时处理引擎,基于事件驱动。而spark streaming是微批次的模型。
架构上的区别主要如下:
1)、架构模型上SparkStreaming在运行时的主要组件包括Master、Worker、Driver、Executor;Flink运行时主要包括:JobManager、TaskManager和Slot。
2)、任务调度上SparkStreaming是连续独断的产生微批次,由DAGScheduler划分stage,然后交由TaskScheduler进行调度。flink根据用户提交的代码生产JobGraph,然后JobManager根据JobGraph生产执行的ExecutionGraph,JobManager根据ExecutionGraph对Job进行调度。
3)、时间语义:SparkStreaming支持的时间机制优先,只支持处理时间。flink时间语义比较丰富,支持事件时间、处理时间、注入时间。同时支持watermark机制来处理滞后数据。
4)容错机制:SparkStreaming通过CheckPoint来进行容错,发生故障的时候可以从CheckPoint处恢复,但是只能保证数据不丢失,但可能会重复处理。flink的checkpoint对source算子偏移量、transform算子的状态、sink算子状态都会记录在checkpoint中,并交由状态后端管理,容错机制更为健全,结合其2PC可以做到端到端的精确一致性。
5)、代码升级更新:由于flink的checkpoint机制可以通过savepoint使其程序的修改升级更为方便,可以对每个有状态算子指定uid,实现程序升级状态保留。

Flink API

最底层是procession API,上层是流处理的dataStream和批处理的dataSet API,再上层是Table API和SQL
dataStream和dataSet的区别
dataStream:是基于无界流的处理,处理一条数据之后,将它序列化到缓存(ResultSubPatition),然后攒一个小批次,通过网络传输到下一个节点由下一个节点继续处理。(适合无界数据、延时低)
dataSet:是基于批处理,处理完一条数据之后,就序列化到缓存,当缓存满了之后再落盘,所有数据处理完之后,再通过网络发送给下一个节点。(适合有界数据、高吞吐)

flink去重的方式

主要方式有三种
1、使用HashSet进行去重,但是需要保存全量数据在内存中,数据量比较大的时候效率很低,容易OOM。
2、使用外部组件:Redis进行去重,通过Redis的set数据结构可以实现去重,但是要涉及和外部的网路通信,会影响实时性。
3、使用Redis的hll数据结构进行近似去重,误差在1%以内。
4、通过布隆过滤器去重:利用flink官方提供的布隆过滤器实现去重,但不是精确的去重,会有一定的误差,但是误差可控。
在这里插入图片描述通过RichFunction将布隆过滤器作为状态保存

RichMapFunction<Tuple4<String, String, String, String>,
Tuple5<String, String, String, String, Long>>() {

            //使用 KeyedState(用于任务失败重启后,从State中恢复数据)
            //1.记录游戏的State
            private transient ValueState<BloomFilter> productState;
            //2.记录次数的State(因为布隆过滤器不会计算它里面到底存了多少数据,所以此处我们创建一个 countState 来计算次数)
            private transient ValueState<Long> countState;

            @Override
            public void open(Configuration parameters) throws Exception {
                //定义一个状态描述器[BloomFilter]
                ValueStateDescriptor<BloomFilter> stateDescriptor = new ValueStateDescriptor<BloomFilter>(
                        "product-state",
                        BloomFilter.class
                );
                //使用RuntimeContext获取状态
                productState = getRuntimeContext().getState(stateDescriptor);

                //定义一个状态描述器[次数]
                ValueStateDescriptor<Long> countDescriptor = new ValueStateDescriptor<Long>(
                        "count-state",
                        Long.class
                );
                //使用RuntimeContext获取状态
                countState = getRuntimeContext().getState(countDescriptor);
            }

通过布隆过滤器找出一定不在集合中的元素
在这里插入图片描述

flink部署模式

主要由standalone模式,yarn模式,k8s模式
yarn session模式

Session-Cluster模式需要先启动集群,然后再提交作业,接着会向yarn申请一块空间后,资源永远保持不变。如果资源满了,下一个作业就无法提交,只能等到yarn中的其中一个作业执行完成后,释放了资源,下个作业才会正常提交。所有作业共享Dispatcher和ResourceManager;共享资源;适合规模小执行时间短的作业
在这里插入图片描述

Per-Job-Cluster 模式

一个Job会对应一个集群,每提交一个作业会根据自身的情况,都会单独向yarn申请资源,直到作业执行完成,一个作业的失败与否并不会影响下一个作业的正常提交和运行。独享Dispatcher和ResourceManager,按需接受资源申请;适合规模大长时间运行的作业。
在这里插入图片描述

flink运行时组件
  • 作业管理器(JobManager)
    每个flink应用都有一个JobManager,主要的工作负责作业开始前接收要执行的应用程序(作业图,配置文件、jar包),将JobGraph转换为物理层面的执行图(ExecutionGraph),包含所有可以执行的并发任务。JobManager向ResourceManager申请执行的必要资源(taskmanager上的slot)。JobManager调度task执行,并负责checkpoint的协调(JobManager负责生产checkpoint)
    JobManager进程内部包含ResourceManager、Dispatcher、JobMaster三个组件。
    ResourceManager:负责和外部资源系统申请管理资源;
    Dispatcher:负责接收客户端的job、启动JobMaster;
    JobMaster:JobMaster和JobManager功能相同,新版本使用的是JobMaster
    在yarn上启动ApplicationMaster之后,会启动一个JobManager进程,JobManager进程中会创建ResourceManager、Dispatcher组件,然后Dispatcher再接受客户端的JobGraph,创建并启动JobMaster。JobMaster将JobGraph根据并行度拆分,创建ExecutionGraph,并通过SlotPool向ResourceManager组件申请slot,然后flink的ResourceManager再通过SlotManager向yarn的ResourceManager申请资源,并在对应TaskManager上启动subtask。

  • 资源管理器(ResourceManager)
    负责管理TaskManager的插槽(slot,slot由slotmanager管理),slot是flink中的处理资源单元,TaskManager将资源平均分配给每个slot(每个slot共享TCP连接,心跳、CPU,减少了网络数据传输),每个slot能够启动一个task,task为线程。ResourceManager负责将空闲插槽分配给JobManager,当插槽不够时,向资源提供平台申请额外的资源。另外,还负责终止空闲的TaskManager释放计算资源。

  • 任务管理器(TaskManager)
    每一个TaskManager都包含了一定数量的插槽(slots),插槽的数量限制了TaskManager能够执行的任务数量。启动之后,TaskManager会向ResourceManager注册它的插槽;ResourceManager会根据JobMatser的请求,分配TaskManager的slot。TaskManager将slot提供给JobMaster
    收到ResourceManager的指令后,TaskManager就会将一个或者多个插槽提供给JobManager调用。JobManager就可以向插槽分配任务(tasks)来执行了。在执行过程中,一个TaskManager可以跟其它运行同一应用程序的TaskManager交换数据。
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

组件通信

flink的组件之间的通信是基于Akka框架,Akka是基于Actor模式构建的异步RPC框架。多个Actor构成ActorSystem,每个Actor有一个邮箱接收其他Actor的消息,Actor之间异步通信。
JobMaster初始化EndPoint的时候通过对方的Service连接对方的Server,连接成功后获得对方的GateWay,然后通过对方的GateWay远程调用对方的方法。
在这里插入图片描述
flink1.10之后将原本的多线程模型改为了介于actor模型的mailbox单线程模型,所有互斥操作以及processInput操作都有一个mailboxProcessor处理

Task任务调度

在flink客户端根据用户代码生成StreamGraph,然后再将一些算子优化合并形成JobGraph,将Job提交给yarn。在JobMaster中,通过DefaultSchedule将JobGraph根据并行度拆分形成ExecutionGraph。然后,JobMaster通过Service链接TaskManager的Server获得对方的GateWay,通过Pipelined策略执行节点部署。
在这里插入图片描述 在这里插入图片描述

Flink内存模型

在这里插入图片描述Flink并不是将对象存储在堆内存上,而是将对象序列化到一个预分配的内存段上,并提供了高效的读写方法,很多运算可以直接操作二进制数据,不需要反序列化。每条数据都会以序列化的形式存储在一个或多个内存段上。如果需要处理的数据多于可以保存在内存中的数据,flink的运算符会将部分数据溢出到磁盘

  • 内存段:内存段在 Flink 内部叫 MemorySegment,是 Flink 中最小的内存分配单元,默认大 小 32KB。它即可以是堆上内存(Java 的 byte 数组),也可以是堆外内存(基于 Netty 的 DirectByteBuffer),同时提供了对二进制数据进行读取和写入的方法。
  • LocalBufferPool:BufferPool 用来管理 Buffer,包含 Buffer 的申请、释放、销毁、可用 Buffer 通知 等,实现类是 LocalBufferPool,每个 Task 拥有自己的 LocalBufferPool
  • NetworkBufferPool:每个 TaskManager 只有一个 NetworkBufferPool。同一个 TaskManager 上的 Task 共享 NetworkBufferPool,在 TaskManager 启动的时候创建并 分配内存。
网络传输中的内存管理
  1. JobGraph被JobMaster根据并行度拆分为ExecutionGraph后,Task从InputGate读取数据,向ResultSubPartition写数据。
  2. Task读取数据的时候,先向InputGate申请数据,InputGate向这个Task的LocalBufferPool读取数据,LocalBufferPool再向TaskManager的NetworkBufferPool读取数据。NetworkBufferPool数据来此Netty的缓冲区。
    在这里插入图片描述
CheckPoint

flink通过checkpoint和状态管理进行容错。当应用被手动停止后,检查点会被删除,可以通过savepoint保存状态,来进行应用的升级。
通过JobManager周期性的创建barrier插入到数据流中,当算子遇见barrier是将算子内部的状态生成一个checkpoint,并同步到远端的状态后端中。对于下图keyby之后一对多的情形,map算子需要等待所有的barrier到齐之后再进行checkpoint,所以先到的那个分区数据会被缓存起来,等上游所以分区的barrier到齐,checkpoint完成之后(完成checkpoint并拷贝到远端文件系统中)汇报给JobManager(JobManager保存所有的状态句柄)并由状态后端管理,再处理分区中的数据。当所有sink算子的checkpoint都完成之后,这个快照完成。如果某个算子checkpoint失败,回滚提交的状态。
在这里插入图片描述
状态后端有三种:MemoryStateBackend、FsStateBackend、RocksStateBacken
MemoryStateBackend:每个算子将状态保存在TaskManager进程的堆内存中。生产检查点的时候,会将状态发送到JobManager并保存在它的堆内存中。JobManager故障就会造成状态丢失,因为算子状态保存在TaskManager的堆内存中,所以状态很大的时候容易OOM。
FsStateBackend:每个算子将本地状态保存在TaskManager进程的堆内存中,在生产检查点的时候,将checkpoint写入远程持久化文件系统中。和MemoryStateBackend一样,当算子状态很大的时候容易造成OOM。
RocksStateBacken:每个算子将状态保存到本地的RocksDB实例中,RocksDB会将数据保存到堆外内存和本地磁盘中,当生成检查点的时候RocksDB会将生成的checkpoint写入远端的文件系统中。因为算子将状态通过RocksDB保存在本地磁盘,所以不会造成OOM,但是会有序列化、反序列化的额外开销。适合大状态应用。同时支持增量检查点。

建议:当状态较小的时候,建议使用FsStateBackend(状态存储在taskmanager内存中,没有序列化的开销);算子状态较大的时候建议使用RocksStateBacken(k-v数据库,内存加磁盘)。

代码中CheckPoint的配置

一般会配置checkpoint的间隔时间、model、超时时间;
由于配合kafka的精确一致性语义需要开启kafka的事务机制,kafka的事务机制默认的是15min的超时时间,超过15minkafka的事务自动关闭,如果flink在15min内checkpoint还没有做完,可能就会造成数据的丢失。因此,需要配置checkpoint的超时时间小于kafka的事务超时时间。
在这里插入图片描述

重启策略

重启策略一般有三种:固定间隔重启、无重启策略、失败率重启

  • 失败率重启:
    失败率重启策略在Job失败后会重启,但是超过失败率后,Job会最终被认定失败。在两个连续的重启尝试之间,重启策略会等待一个固定的时间
   env.setRestartStrategy(RestartStrategies.failureRateRestart(  3,//一个时间段内的最大失败次数  
Time.of(5, TimeUnit.MINUTES), // 衡量失败次数的是时间段  Time.of(10, TimeUnit.SECONDS) // 间隔  ));

  • 固定间隔重启:默认使用固定间隔重启策略
env.setRestartStrategy(RestartStrategies.fixedDelayRestart( 3,// 尝试重启的次数 
    Time.of(10, TimeUnit.SECONDS) // 间隔 ));
  • 无重启策略:一般没有设置checkpoint的时候,默认无重启策略
检查点对性能的影响

任务在生产检查点的时候会处于阻塞状态,完成之后任务才会再执行,同时生成检查点会通过网络写入远程的存储系统,会增加延迟。
异步生产检查点:FsStateBackend和RocksStateBacken都支持异步生产检查点,当检查点生产触发的时候,状态后端会为当前状态创建一个本地备份,备份创建完成之后task就可以继续运行。后台进程异步将本地状态拷贝到远程存储。同时,RocksDBStateBackend还支持增量生产检查点,可以有效地降低需要传输的数据量。

非对齐CheckPoint:对于反压比较严重的任务可以开启非对齐CheckPoint,可以减轻由于barrier对齐对反压的影响。

Exectly-once 两种实现方式

两阶段提交法
当外部系统不支持事务的时候只有使用预写日志的方式,预写日志就是将最后sink的结果数据先当做状态保存,然后收到jobmanager的完成通知之后,批量的一次性写入外部sink系统。
预写日志存在的问题:批量写出的时候可能写到一半的就挂掉,所以只能保证At-Least-Once(至少一次),不能保证精确一致性。同时,批量写的延迟比较高,会损失实时性。

sink端开启事务支持
通过kafka的事务机制和flink的checkpoint完成
前提:checkpoint的超时时间需要小于kafka事务的超时时间

  • 第一条数据来了之后,sink算子开启一个kafka事务,将数据正常写入kafka分区日志并标记为未提交(预提交)。
  • JobManager触发checkpoint操作,发出barrier,barrier随着数据向下游传递,遇见barrier的算子将状态封装成checkpoint并保存到状态后端,并通知jobmanager。
  • 当jobmanager收到所有算子的任务通知之后,向各个TaskManager发送确认信息,表示checkpoint完成
  • sink算子收到jobmanager的确认信息之后,正式提交这段时间的数据,并开启一个新的事务(遇见barrier就开启一个新的事务),下一轮的数据存入新的事务。
  • 外部kafka关闭事务,提交的数据可以正常消费

WaterMark

flink中引入watermark机制延迟关闭时间窗口,每个task为上游的多个分区创建一个分区watermark,然后根据将分区watermark的最小值广播到下游。
watermark的生成有周期性的生成watermark和每条数据都会生成一个watermark
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

分区策略

在流进行转换操作后,Flink通过分区器来精确得控制数据流向。分区策略可以在算子后面指定。默认的是forwardpartitioner(上下游分区一直)、rebalancepartitioner(上下游分区不一致)、keygroupstreampartitioner(byKey算子)
1、全局分区器GlobalPartitioner
将所有数据都发送到下游的某个算子实例(subtask id=0)
在这里插入图片描述

2、随机分区器ShufflePartitioner
根据均匀分布对元素进行随机划分
在这里插入图片描述
3、广播分区器BroadcastPartitioner
发送给下游所有算子实例

在这里插入图片描述
4、重行分区 RebalancePartitioner
通过循环的方式一次发送到下游的task,在上下游没有指定分区器的情况下(并且不是byKey算子),如果上下游算子并行度不一致,则使用RebalancePartitioner。
在这里插入图片描述
5、KeyGroupStreamPartitioner
根据key的分组索引选择发送到相应的下游subtask,byKey算子使用KeyGroupStreamPartitioner

6、ForwardPartitioner
在上下游没有指定分区器的情况下,如果上下游的算子并行度一致,则使用ForwardPartitioner在这里插入图片描述
7、RescalePartitioner
基于上下游算子的并行度,将记录以循环的方式输出到下游算子的每个实例。
举例:上游并行度是2,下游是4,则上游一个分区以循环的方式将记录输出到下游的两个分区中,另一个分区以循环的方式将记录输出到下游的另两个分区中。
若上游并行度时4,下游并行度时2,则上游两个分区将记录输出到下游一个分区中,上游另两个分区将记录输出到下游另一个分区中。
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值