Flink学习笔记

文章目录

Flink介绍

flink是什么

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

Flink简介

Apache Flink是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。Flink设计为在所有常见的集群环境中运行,以内存速度和任何规模执行计算。

  1. 无界流和有界流
    任何类型的数据都是作为事件流产生的。信用卡交易,传感器测量,机器日志或网站或移动应用程序上的用户交互,所有这些数据都作为流生成。

    数据可以作为无界或有界流处理。

    1. 无界流有一个开始但没有定义的结束。它们不会在生成时终止并提供数据。必须持续处理无界流,即必须在摄取事件后立即处理事件。无法等待所有输入数据到达,因为输入是无界的,并且在任何时间点都不会完成。处理无界数据通常要求以特定顺序(例如事件发生的顺序)摄取事件,以便能够推断结果完整性。

    2. 有界流具有定义的开始和结束。可以在执行任何计算之前通过摄取所有数据来处理有界流。处理有界流不需要有序摄取,因为可以始终对有界数据集进行排序。有界流的处理也称为批处理。

    Apache Flink擅长处理无界和有界数据集。精确控制时间和状态使Flink的运行时能够在无界流上运行任何类型的应用程序。有界流由算法和数据结构内部处理,这些算法和数据结构专门针对固定大小的数据集而设计,从而产生出色的性能。

为什么要用flink

  • 提供准确的结果,即使在无序或延迟数据的情况下也是如此
  • 具有状态和容错能力,可以在保持应用状态的同时无故障地从故障中恢复
  • 大规模执行,在数千个节点上运行,具有非常好的吞吐量和延迟特性

Flink技术特点

  1. 流处理特性
支持高吞吐、低延迟、高性能的流处理

支持带有事件时间的窗口(Window)操作

支持有状态计算的Exactly-once语义

支持高度灵活的窗口(Window)操作,支持基于time、count、session,以及data-driven的窗口操作

支持具有Backpressure功能的持续流模型

支持基于轻量级分布式快照(Snapshot)实现的容错

一个运行时同时支持Batch on Streaming处理和Streaming处理

Flink在JVM内部实现了自己的内存管理

支持迭代计算

支持程序自动优化:避免特定情况下Shuffle、排序等昂贵操作,中间结果有必要进行缓存
  1. API支持
对Streaming数据类应用,提供DataStream API

对批处理类应用,提供DataSet API(支持Java/Scala)
  1. Libraries支持
支持机器学习(FlinkML)

支持图分析(Gelly)

支持关系数据处理(Table)

支持复杂事件处理(CEP)
  1. 整合支持
支持Flink on YARN

支持HDFS

支持来自Kafka的输入数据

支持Apache HBase

支持Hadoop程序

支持Tachyon

支持ElasticSearch

支持RabbitMQ

支持Apache Storm

支持S3

支持XtreemFS

flink基本架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a6DLxWsW-1626944190905)(picture/flink作业提交架构流程.jpg)]
1、Program Code:我们编写的 Flink 应用程序代码

2、Job Client:Job Client 不是 Flink 程序执行的内部部分,但它是任务执行的起点。 Job Client 负责接受用户的程序代码,然后创建数据流,将数据流提交给 Job Manager 以便进一步执行。 执行完成后,Job Client 将结果返回给用户

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UA1wFCG6-1626944190912)(picture/taskManager.jpg)]
3、Job Manager:主进程(也称为作业管理器)协调和管理程序的执行。 它的主要职责包括安排任务,管理checkpoint ,故障恢复等。机器集群中至少要有一个 master,master 负责调度 task,协调 checkpoints 和容灾,高可用设置的话可以有多个 master,但要保证一个是 leader, 其他是 standby; Job Manager 包含 Actor system、Scheduler、Check pointing 三个重要的组件
4、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。这种共享可以有更好的资源利用率。

  • JobManager

    JobManager是flink系统的协调者,它负责接收Flink Job的状态信息,并管理Flink集群中从节点TaskManager。

  • TaskManager
    TaskManager是一个Actor,负责实际进行计算。它分为两个阶段:

    • 注册阶段
      TaskManager会向JobManager注册,发送RegesterTaskManager消息,等待JobManager返回AcknowledgeRegistration,然后TaskManager就可以进行初始化过程。
    • 可操作阶段
      该阶段TaskManager可以接收并处理与Task有关的消息,如SubmitTask、CancelTask、FailTask。如果TaskManager无法连接到JobManager,这是TaskManager就失去了与JobManager的联系,会自动进入“注册阶段”,只有完成注册才能继续处理Task相关的消息。
  • Client
    当一个用户提交一个Flink程序时,会首先创建一个Client,该Client首先会对用户提交的Flink程序进行预处理,并提交到Flink集群中处理,所以Client需要从用户提交的Flink程序配置中获取JobManager的地址,并建立到JobManager的链接,将Flink Job提交给JobManager。

无界流有一个开始但没有定义的结束。它们不会在生成时终止并提供数据。必须持续处理无界流,即必须在摄取事件后立即处理事件。无法等待所有输入数据到达,因为输入是无界的,并且在任何时间点都不会完成。处理无界数据通常要求以特定顺序(例如事件发生的顺序)摄取事件,以便能够推断结果完整性。

有界流具有定义的开始和结束。可以在执行任何计算之前通过摄取所有数据来处理有界流。处理有界流不需要有序摄取,因为可以始终对有界数据集进行排序。有界流的处理也称为批处理。

构建项目

如果你想 构建/打包你的项目,进入到你的项目目录并且执行 ‘mvn clean package’ 命令。 你将 找到一个 JAR 文件,包含了您的应用、扩展的 connectors 以及程序依赖的Libraries到您的应用程序:target/-.jar。

注意: 如果您使用和 StreamingJob 不同的类作为应用程序的主类/入口,我们建议您相应的修改 pom.xml 中的 mainClass 设置。那样,Flink 在运行应用程序的 JAR 文件的时候不需要再指定主类。

Flink 部署及运行

  1. 查看Flink进程的启动参数
jps

ps aux | grep 4717

ps aux | grep 4203
  1. 日志的查看和配置
    log 目录中以“flink- u s e r − s t a n d a l o n e s e s s i o n − {user}-standalonesession- userstandalonesession{id}-${hostname}”为前缀的文件对应的即是
    JobManager的输出,其中有三个文件:
●  flink-${user}-standalonesession-${id}-${hostname}.log:代码中的日志输出
●  flink-${user}-standalonesession-${id}-${hostname}.out:进程执行时的stdout输出
●  flink-${user}-standalonesession-${id}-${hostname}-gc.log:JVM的GC的日志

log 目录中以“flink- u s e r − t a s k e x e c u t o r − {user}-taskexecutor- usertaskexecutor{id}-${hostname}”为前缀的文件对应的是TaskManager的输出,也包括三个文件,和JobManager的输出一致。

需要注意的是,“flink- u s e r − s t a n d a l o n e s e s s i o n − {user}-standalonesession- userstandalonesession{id}- h o s t n a m e ”和“ f l i n k − {hostname}”和“flink- hostnameflink{user}-taskexecutor- i d − {id}- id{hostname}”都带有“ i d ”,“ {id}”,“ id{id}”表示本进程在本机上该角色(JobManager
或TaskManager)的所有进程中的启动顺序,默认从0开始。
在/tmp目录下每个角色(TM或者JM)都有一个pid 文件,追加记录着每个启动过的进
程pid。需要注意的是,因为pid 文件在/tmp这个公共的目录下,所以即使是你在用不同Flink
binary执行start-cluster都会发现这个情况,特别是在之前忘了stop-cluster的情况下。

  1. 多机部署Flink standalone集群
    部署前要注意的要点:
    ● 每台机器上配置好java以及JAVA_HOME环境变量
    ● 最好挑选一台机器,和其他机器ssh打通
    每台机器上部署的Flink binary的目录要保证是同一个目录
    ● 如果需要用hdfs,需要配置HADOOP_CONF_DIR环境变量配置上

如果想尝试试用Flink standalone HA模式,需要确保基于flink release-1.6.1及以上版本,因为这里社区有个bug会导致这个模式下主JobManager不能正常工作。下载时候需要注意,接下来的实验中需要用到hdfs,所以需要下载带有hadoop支持的flink binary包。否则,接下来初始化standalone集群的时候会报错,初始化hdfs filesystem失败。

Flink基本概念

Flink DataStream操作

1. 分布式流处理的基本模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lBvnjmcX-1626944190916)(picture/分布式流处理的基本模型.png)]

2. Flink DataStream程序结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NBskMfoO-1626944190921)(picture/Flink-DataStream程序结构.png)]

3.DataStream操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ksinU4Zz-1626944190925)(picture/dataStream操作.png)]

4. 理解KeyedStream

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3naS6IMx-1626944190931)(picture/理解KeyedStream.png)]

检查点

检查点通过允许恢复状态和相应的流位置使Flink中的状态容错,从而为应用程序提供与无故障执行相同的语义。

保存检查点

默认情况下,检查点不会保存,仅用于从失败中恢复作业。取消程序时会删除它们。但是,您可以配置要保存的定期检查点。根据配置 ,当作业失败或取消时,不会自动清除这些保存的检查点。这样,如果您的工作失败,您将有一个检查点可以从中恢复。

CheckpointConfig config = env.getCheckpointConfig();
config.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

该ExternalizedCheckpointCleanup模式配置取消作业时检查点发生的情况:

  • ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION:取消作业时保存检查点。请注意,在这种情况下,您必须在取消后手动清理检查点状态。

  • ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION:取消作业时删除检查点。只有在作业失败时,检查点状态才可用。

目录结构

与保存点类似,检查点由元数据文件和一些其他数据文件组成,具体取决于状态后台。元数据文件和数据文件存储在state.checkpoints.dir配置文件中配置的目录中,也可以为代码中的每个作业指定。
通过配置文件全局配置

state.checkpoints.dir: hdfs:///checkpoints/

在构造状态后台时为每个作业配置

env.setStateBackend(new RocksDBStateBackend("hdfs:///checkpoints-data/");

从保存的检查点恢复

通过使用检查点的元数据文件,可以从检查点恢复作业,就像从保存点恢复一样(请参阅 保存点恢复指南)。请注意,如果元数据文件不是自包含的,则JobManager需要访问它所引用的数据文件(请参阅 上面的目录结构)。

$ bin/flink run -s :checkpointMetaDataPath [:runArgs]

保存点

保存点是外部存储的自包含检查点,可用于停止和恢复或更新Flink程序。他们使用Flink的检查点机制来创建流程序状态的(非增量)SNAPSHOT,并将检查点数据和元数据写入外部文件系统。

savepoint 和 checkpoint 区别

从概念上讲,Flink的Savepoints与Checkpoints的不同之处在于备份与传统数据库系统中的恢复日志不同。检查点的主要目的是在意外的作业失败时提供恢复机制。Checkpoint的生命周期由Flink管理,即Flink创建,拥有和发布Checkpoint - 无需用户交互。作为一种恢复和定期触发的方法,Checkpoint实现的两个主要设计目标是:i)being as lightweight to create (轻量级),ii)fast restore (快速恢复) 。针对这些目标的优化可以利用某些属性,例如,JobCode在执行尝试之间不会改变。 在用户终止作业后,通常会删除 Checkpoint(除非明确配置为保留的 Checkpoint)。

与此相反,Savepoints由用户创建,拥有和删除。他们的用例是planned (计划) 的,manual backup( 手动备份 ) 和 resume(恢复) 。例如,这可能是您的Flink版本的更新,更改您的Job graph ,更改 parallelism ,分配第二个作业,如红色/蓝色部署,等等。当然,Savepoints必须在终止工作后继续存在。从概念上讲,保存点的生成和恢复成本可能更高,并且更多地关注可移植性和对前面提到的作业更改的支持。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CNwEZrEn-1626944190933)(picture/checkpoint和savepoint区别.jpg)]
除去这些概念上的差异,Checkpoint 和 Savepoint 的当前实现基本上使用相同的代码并生成相同的格式

Assigning Operator IDs

强烈建议您按照本节所述调整程序,以便将来能够升级程序。需要进行的主要更改是通过uid(String)方法手动指定操作符id。这些id用于确定每个操作符的状态。

DataStream<String> stream = env.
  // Stateful source (e.g. Kafka) with ID
  .addSource(new StatefulSource())
  .uid("source-id") // ID for the source operator
  .shuffle()
  // Stateful mapper with ID
  .map(new StatefulMapper())
  .uid("mapper-id") // ID for the mapper
  // Stateless printing sink
  .print(); // Auto-generated ID

如果不手动指定id,它们将自动生成。只要这些id没有更改,就可以从保存点自动恢复。生成的id取决于程序的结构,并且对程序更改非常敏感。因此,强烈建议手动分配这些id。

触发保存点

触发保存点时,会创建一个新的保存点目录,其中将存储数据和元数据。可以通过配置默认目标目录或使用触发器命令指定自定义目标目录来控制此目录的位置(请参阅:targetDirectory参数)。

checkpoint执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5HZ13F4Z-1626944190935)(picture/checkpoint执行流程.webp)]
每一个 Flink 作业都会有一个 JobManager ,JobManager 里面的 checkpoint coordinator 管理整个作业的 checkpoint 过程。用户通过 env 设置 checkpoint 的时间间隔,使得 checkpoint coordinator 定时将 checkpoint 的 barrier 发送给每个 source subtask。

当 source 算子实例收到一个 barrier 时,它会暂停自身的数据处理,然后将自己的当前 缓存数据 state 保存为快照 snapshot,并且持久化到指定的存储,最后算子实例向 checkpoint coordinator 异步发送一个确认信号 ack,同时向所有下游算子广播该 barrier 和恢复自身的数据处理。

以此类推,每个算子不断制作 snapshot 并向下游广播 barrier,直到 barrier 传递到 sink 算子实例,此时确定全局快照完成。

参考博客:https://www.jianshu.com/p/5ed18ab7fe3d

Checkpoint Expire 过期

如果 Checkpoint 做的非常慢,超过了 timeout 还没有完成,则整个 Checkpoint 也会失败。例如,如果 Checkpoint 21 由于超时而失败是,jobmanager.log 的关键日志如下。

// 表示 Chekpoint 21 由于超时而没有完成
Checkpoint 21 of job 85d268e6fbc19411185f7e4868a44178  expired before completing.

// 表示 超时 Checkpoint 是来自 job id 为 85d268e6fbc19411185f7e4868a44178, subtask 为 0b60f08bf8984085b59f8d9bc74ce2e1 
Received late message for now expired checkpoint attempt 21 from 0b60f08bf8984085b59f8d9bc74ce2e1 of job 85d268e6fbc19411185f7e4868a44178.

接着打开 debug 级别的日志, taskmananger.log 的 snapshot 分为三个阶段,开始 snapshot 前,同步阶段,异步阶段:

DEBUG
Starting checkpoint (6751) CHECKPOINT on task taskNameWithSubtasks (4/4)
// 该日志表示 TM 端 barrier 对齐后,准备开始做 Checkpoint,其中6751是checkpoint id,CHECKPOINT是类型,taskNameWithSubtasks是subtask name

DEBUG
2019-08-06 13:43:02,613 DEBUG org.apache.flink.runtime.state.AbstractSnapshotStrategy       - DefaultOperatorStateBackend snapshot (FsCheckpointStorageLocation {fileSystem=org.apache.flink.core.fs.SafetyNetWrapperFileSystem@70442baf, checkpointDirectory=xxxxxxxx, sharedStateDirectory=xxxxxxxx, taskOwnedStateDirectory=xxxxxx, metadataFilePath=xxxxxx, reference=(default), fileStateSizeThreshold=1024}, synchronous part) in thread Thread[Async calls on Source: xxxxxx
_source -> Filter (27/70),5,Flink Task Threads] took 0 ms.
// 该日志表示当前这个 backend 的同步阶段完成,共使用了 0 ms
// 说明: fink-config.yaml的state.backend.async配置异步/同步snapshot,默认是异步snapshot

DEBUG
DefaultOperatorStateBackend snapshot (FsCheckpointStorageLocation {fileSystem=org.apache.flink.core.fs.SafetyNetWrapperFileSystem@7908affe, checkpointDirectory=xxxxxx, sharedStateDirectory=xxxxx, taskOwnedStateDirectory=xxxxx,  metadataFilePath=xxxxxx, reference=(default), fileStateSizeThreshold=1024}, asynchronous part) in thread Thread[pool-48-thread-14,5,Flink Task Threads] took 369 ms
// 该日志表示异步阶段完成,异步阶段使用了 369 ms

Checkpoint 的配置原则

上一节介绍了 Checkpoint 的配置方法,以及 Checkpoint 时间间隔与 Checkpoint 生产时间的关系对 Flink 应用程序的影响。Checkpoint 的配置需要随着 Flink 应用程序的不同而不同。这里简单介绍一下 Checkpoint 的配置原则:

  1. Checkpoint 时间间隔不易过大。一般来说,Checkpoint 时间间隔越长,需要生产的 State 就越大。如此一来,当失败恢复时,需要更长的追赶时间。
    Checkpoint 时间间隔不易过小。如果 Checkpoint 时间间隔太小,那么 Flink 应用程序就会频繁 Checkpoint,导致部分资源被占有,无法专注地进行数据处理。
  2. Checkpoint 时间间隔大于 Checkpoint 的生产时间。当 Checkpoint 时间间隔比 Checkpoint 生产时间长时,在上次 Checkpoint 完成时,不会立刻进行下一次 Checkpoint,而是会等待一段时间,之后再进行新的 Checkpoint。否则,每次 Checkpoint 完成时,就会立即开始下一次 Checkpoint,系统会有很多资源被 Checkpoint 占用,而真正任务计算的资源就会变少。
  3. 开启本地恢复。如果 Flink State 很大,在进行恢复时,需要从远程存储上读取 State 进行恢复,如果 State 文件过大,此时可能导致任务恢复很慢,大量的时间浪费在网络传输方面。此时可以设置 Flink 应用程序本地 State 恢复,应用程序 State 本地恢复默认没有开启,可以设置参数 state.backend.local-recovery 值为 true 进行激活。
  4. 设置 Checkpoint 保存数。Checkpoint 保存数默认是 1,也就是只保存最新的 Checkpoint 的 State 文件,当进行 State 恢复时,如果最新的 Checkpoint 文件不可用时 (比如文件损坏或者其他原因),那么 State 恢复就会失败,如果设置 Checkpoint 保存数 3,即使最新的 Checkpoint 恢复失败,那么 Flink 也会回滚到上一次 Checkpoint 的状态文件进行恢复。考虑到这种情况,可以通过 state.checkpoints.num-retained 设置 Checkpoint 保存数。

状态后台

可用的状态后台

开箱即用,Flink捆绑了这些状态后台:

  • MemoryStateBackend
  • FsStateBackend
  • RocksDBStateBackend

如果没有配置其他任何内容,系统将使用MemoryStateBackend。

MemoryStateBackend

该MemoryStateBackend保存数据在内部作为Java堆的对象。键/值状态和窗口 算子包含存储值,触发器等的哈希表。

在检查点时,此状态后台将对状态进行SNAPSHOT,并将其作为检查点确认消息的一部分发送到JobManager(主服务器),JobManager也将其存储在其堆上。

FsStateBackend

所述FsStateBackend配置有文件系统URL(类型,地址,路径),如“HDFS://名称节点:40010 /Flink/检查点”或“文件:///数据/Flink/检查点”。

FsStateBackend将正在运行的数据保存在TaskManager的内存中。在检查点时,它将状态SNAPSHOT写入配置的文件系统和目录中的文件。最小元数据存储在JobManager的内存中(或者,在高可用性模式下,存储在元数据检查点中)。

RocksDBStateBackend

所述RocksDBStateBackend配置有文件系统URL(类型,地址,路径),如“HDFS://名称节点:40010 /Flink/检查点”或“文件:///数据/Flink/检查点”。

RocksDBStateBackend将RocksDB数据库中的飞行中数据保存在(默认情况下)存储在TaskManager数据目录中。在检查点时,整个RocksDB数据库将被检查点到配置的文件系统和目录中。最小元数据存储在JobManager的内存中(或者,在高可用性模式下,存储在元数据检查点中)。

RocksDBStateBackend始终执行异步SNAPSHOT。

状态存储策略选择

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zAKAge6Y-1626944190939)(picture/存储状态策略选择.jpeg)]
关于状态存储策略选择,生产环境状态存储 Backend 有两种方式:

  • FsStateBackend:State 存储在内存,Checkpoint 时持久化到 HDFS;

  • RocksDBStateBackend:State 存储在 RocksDB 实例,可增量 Checkpoint,适合超大 State。在广告场景下展现流 20 分钟数据有 1 TB 以上,从节省内存等方面综合考虑,快手最终选择的是 RocksDBStateBackend。

配置状态后台

如果您不指定任何内容,则默认状态后台是JobManager。如果要为群集上的所有作业建立不同的默认值,可以通过在flink-conf.yaml中定义新的默认状态后台来实现。可以基于每个作业覆盖默认状态后台,如下所示。

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));

auto.offset.reset 的值更改为:earliest,latest,和none (offest保存在kafka的一个特殊的topic名为:__consumer_offsets里面)

Apache Kafka连接器

Flink提供了Kafka Connector用于消费/生产Appache Kafka topic的数据,Flink的Kafka consumer集成了checkpoint机制以提供精确一次的处理语义。在具体的实现过程中,Flink不依赖于Kafka内置的消费者位移管理,而是在内存自行记录和维护consumer的位移。

为了保证 Flink 程序的 exactly-once,必须由各个 Kafka source 算子维护当前算子所消费的 partition 消费 offset 信息,并在每次checkpoint 时将这些信息写入到 state 中, 在从 checkpoint 恢复中从上次 commit 的位点开始消费,保证 exactly-once. 如果用 Kafka 消费组管理,那么 FlinkKafkaConsumer 内各个并发实例所分配的 partition 将由 Kafka 的消费组管理,且 offset 也由 Kafka 消费组管理者记录,Flink 无法维护这些信息。

为了保证exactly-once,flink自己通过barrier来实现checkpoint,包括barrier的传递等等,所以flink在kafkaconsumer的基础之上,封装了一层语义保障。

Kafka消费者开始位置配置

Flink Kafka Consumer允许配置如何确定Kafka分区的起始位置。

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

FlinkKafkaConsumer08<String> myConsumer = new FlinkKafkaConsumer08<>(...);
myConsumer.setStartFromEarliest();     // start from the earliest record possible
myConsumer.setStartFromLatest();       // start from the latest record
myConsumer.setStartFromTimestamp(...); // start from specified epoch timestamp (milliseconds)
myConsumer.setStartFromGroupOffsets(); // the default behaviour

DataStream<String> stream = env.addSource(myConsumer);

Flink Kafka Consumer的所有版本都具有上述明确的起始位置配置方法。

  • setStartFromGroupOffsets(默认行为):从group.id Kafka代理(或Zookeeper for Kafka 0.8)中的消费者组(在消费者属性中设置)提交的偏移量开始读取分区。如果找不到分区的偏移量,auto.offset.reset将使用属性中的设置。
    setStartFromEarliest()/ setStartFromLatest():从最早/最新记录开始。在这些模式下,Kafka中的承诺偏移将被忽略,不会用作起始位置。
    setStartFromTimestamp(long):从指定的时间戳开始。对于每个分区,时间戳大于或等于指定时间戳的记录将用作起始位置。如果分区的最新记录早于时间戳,则只会从最新记录中读取分区。在此模式下,Kafka中的已提交偏移将被忽略,不会用作起始位置。

请注意 ,当作业从故障中自动恢复或使用保存点手动恢复时,这些起始位置配置方法不会影响起始位置。在恢复时,每个Kafka分区的起始位置由存储在保存点或检查点中的偏移量确定(有关检查点的信息,请参阅下一节以启用消费者的容错)。
另外,当任务自动地从失败中恢复或手动地从savepoint中恢复时,上述这些设置位移的方法是不生效的。在恢复时,每个Kafka分区的起始位移都是由保存在savepoint或checkpoint中的位移来决定的。

Kafka消费者和容错

一旦启动了Flink的检查点机制(checkpointing),Flink kafka消费者会定期地对齐消费的topic做checkpoint以保存它消费的位移以及其操作的状态。一旦出现失败,Flink将会恢复streaming程序到最新的checkpoint状态,然后重新从Kafka消费数据,重新读取的位置就是保存在checkpoint中的位移。

启用Flink的检查点后,Flink Kafka Consumer将使用主题中的记录,并以一致的方式定期检查其所有Kafka偏移以及其他 算子操作的状态。如果作业失败,Flink会将流式程序恢复到最新检查点的状态,并从存储在检查点中的偏移量开始重新使用来自Kafka的记录。

因此,绘制检查点的间隔定义了程序在发生故障时最多可以返回多少。

要使用容错的Kafka使用者,需要在运行环境中启用拓扑的检查点:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(5000); // checkpoint every 5000 msecs

另请注意,如果有足够的处理插槽可用于重新启动拓扑,则Flink只能重新启动拓扑。因此,如果拓扑由于丢失了TaskManager而失败,那么之后仍然必须有足够的可用插槽。YARN上的Flink支持自动重启丢失的YARN容器。

如果未启用检查点,Kafka使用者将定期向Zookeeper提交偏移量。

Kafka消费者topic和分区发现
分区发现

Flink Kafka Consumer支持发现动态创建的Kafka分区,并使用一次性保证来消耗它们。在初始检索分区元数据之后(即,当作业开始运行时)发现的所有分区将从最早可能的偏移量中消耗。
默认情况下,禁用分区发现。要启用它,请flink.partition-discovery.interval-millis在提供的属性配置中设置非负值,表示以毫秒为单位的发现间隔。

topic发现

在更高级别,Flink Kafka Consumer还能够使用正则表达式基于主题名称的模式匹配来发现主题。请参阅以下示例:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
properties.setProperty("group.id", "test");

FlinkKafkaConsumer011<String> myConsumer = new FlinkKafkaConsumer011<>(
    java.util.regex.Pattern.compile("test-topic-[0-9]"),
    new SimpleStringSchema(),
    properties);

DataStream<String> stream = env.addSource(myConsumer);
...

在上面的示例中,test-topic-当作业开始运行时,消费者将订阅名称与指定正则表达式匹配的所有主题(以单个数字开头并以单个数字结尾)。

要允许使用者在作业开始运行后发现动态创建的主题,请为其设置非负值flink.partition-discovery.interval-millis。这允许使用者发现名称也与指定模式匹配的新主题的分区。

Kafka consumer位移提交

Flink Kafka consumer可以自行设置如何将偏移提交回Kafka代理(或0.8中的Zookeeper)的行为。请注意,Flink Kafka Consumer不依赖于承诺的偏移量来实现容错保证。这些提交位移只是供监控使用。

配置位移提交的方法各异,主要依赖于是否启用了checkpointing机制:

  • 未启用checkpointing:Flink Kafka consumer依赖于Kafka提供的自动提交位移功能。设置方法是在Properties对象中配置Kafka参数enable.auto.commit(新版本Kafka consumer)或auto.commit.enable(老版本Kafka consumer)
  • 启用checkpointing:Flink Kafka consumer会提交位移到checkpoint状态中。这就保证了Kafka中提交的位移与checkpoint状态中的位移是一致的。用户可以调用setCommitOffsetsCheckpoints(boolean)方法来禁用/开启位移提交——默认是true,即开启了位移提交。注意,这种情况下,Flink会忽略上一种情况中提及的Kafka参数
Kafka consumer时间戳提取/水位生成

通常,事件或记录的时间戳信息是封装在消息体中。至于水位,用户可以选择定期地发生水位,也可以基于某些特定的Kafka消息来生成水位——这分别就是AssignerWithPeriodicWatermaks以及AssignerWithPunctuatedWatermarks接口的使用场景。

用户也能够自定义时间戳提取器/水位生成器,具体方法参见这里,然后按照下面的方式传递给consumer:

	
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
// only required for Kafka 0.8
properties.setProperty("zookeeper.connect", "localhost:2181");
properties.setProperty("group.id", "test");
 
FlinkKafkaConsumer08<String> myConsumer =
    new FlinkKafkaConsumer08<>("topic", new SimpleStringSchema(), properties);
myConsumer.assignTimestampsAndWatermarks(new CustomWatermarkEmitter());
 
DataStream<String> stream = env
    .addSource(myConsumer)
    .print();

在内部,Flink会为每个Kafka分区都执行一个对应的assigner实例。一旦指定了这样的assigner,对于每条Kafka中的消息,extractTimestamp(T element, long previousElementTimestamp)方法会被调用来给消息分配时间戳,而getCurrentWatermark()方法(定时生成水位)或checkAndGetNextWatermark(T lastElement, long extractedTimestamp)方法(基于特定条件)会被调用以确定是否发送新的水位值。

Kafka生产者

Flink的Kafka Producer被称为FlinkKafkaProducer011(或010Kafka 0.10.0.x等)。它允许将记录流写入一个或多个Kafka主题。

例:

DataStream<String> stream = ...;

FlinkKafkaProducer011<String> myProducer = new FlinkKafkaProducer011<String>(
        "localhost:9092",            // broker list
        "my-topic",                  // target topic
        new SimpleStringSchema());   // serialization schema

// versions 0.10+ allow attaching the records' event timestamp when writing them to Kafka;
// this method is not available for earlier Kafka versions
myProducer.setWriteTimestampToKafka(true);

stream.addSink(myProducer);
在Kafka 0.10中使用Kafka时间戳和Flink事件时间

自Apache Kafka 0.10+以来,Kafka的消息可以携带时间戳,指示事件发生的时间(请参阅Apache Flink中的“事件时间”)或消息写入Kafka代理的时间。

在FlinkKafkaConsumer010将发射记录附有时间戳,如果在Flink时间特性被设定为TimeCharacteristic.EventTime(StreamExecutionEnvironment.setStreamTimeCharacteristic(TimeCharacteristic.EventTime))

state存储

Flink的检查点的触发,会生成分布式快照,而快照中除了系统运行时的一些元数据信息外,就是程序中各种状态的数据了,例如window中的数据,UDF的方法中的数据等。目的是在恢复时实现exactly-once语义的处理。

Flink支持有状态的operator以及UDF的state,而支持的state backend包括3种方式:

1、JobManager memory(当前版本的默认模式)
2、HDFS
3、RocksDB(未来Flink版本的默认模式)

Flink Data transformation(转换)

数据转换图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6a05kCpI-1626944190941)(picture/datatransform.png)]

Window

Window 函数允许按时间或其他条件对现有 KeyedStream 进行分组。 以下是以 10 秒的时间窗口聚合:

inputStream.keyBy(0).window(Time.seconds(10));

Flink 定义数据片段以便(可能)处理无限数据流。 这些切片称为窗口。 此切片有助于通过应用转换处理数据块。 要对流进行窗口化,我们需要分配一个可以进行分发的键和一个描述要对窗口化流执行哪些转换的函数

要将流切片到窗口,我们可以使用 Flink 自带的窗口分配器。 我们有选项,如 tumbling windows, sliding windows, global 和 session windows。 Flink 还允许您通过扩展 WindowAssginer 类来编写自定义窗口分配器。 这里先预留下篇文章来讲解这些不同的 windows 是如何工作的。

WindowAll

windowAll 函数允许对常规数据流进行分组。 通常,这是非并行数据转换,因为它在非分区数据流上运行。

与常规数据流功能类似,我们也有窗口数据流功能。 唯一的区别是它们处理窗口数据流。 所以窗口缩小就像 Reduce 函数一样,Window fold 就像 Fold 函数一样,并且还有聚合。

inputStream.keyBy(0).windowAll(Time.seconds(10));

Union

Union 函数将两个或多个数据流结合在一起。 这样就可以并行地组合数据流。 如果我们将一个流与自身组合,那么它会输出每个记录两次。

inputStream.union(inputStream1, inputStream2, ...);

Window join

我们可以通过一些 key 将同一个 window 的两个数据流 join 起来。

inputStream.join(inputStream1)
           .where(0).equalTo(1)
           .window(Time.seconds(5))     
           .apply (new JoinFunction () {...});

Split

此功能根据条件将流拆分为两个或多个流。 当您获得混合流并且您可能希望单独处理每个数据流时,可以使用此方法。

SplitStream<Integer> split = inputStream.split(new OutputSelector<Integer>() {
    @Override
    public Iterable<String> select(Integer value) {
        List<String> output = new ArrayList<String>(); 
        if (value % 2 == 0) {
            output.add("even");
        }
        else {
            output.add("odd");
        }
        return output;
    }
});

Select

此功能允许您从拆分流中选择特定流。

SplitStream<Integer> split;
DataStream<Integer> even = split.select("even"); 
DataStream<Integer> odd = split.select("odd"); 
DataStream<Integer> all = split.select("even","odd");

Project

Project 函数允许您从事件流中选择属性子集,并仅将所选元素发送到下一个处理流。

DataStream<Tuple4<Integer, Double, String, String>> in = // [...] 
DataStream<Tuple2<String, String>> out = in.project(3,2);

上述函数从给定记录中选择属性号 2 和 3。 以下是示例输入和输出记录:

(1,10.0,A,B)=> (B,A)
(2,20.0,C,D)=> (D,C)

Flink 中几种 Time 详解

Processing Time

Processing Time 是指事件被处理时机器的系统时间。

当流程序在 Processing Time 上运行时,所有基于时间的操作(如时间窗口)将使用当时机器的系统时间。每小时 Processing Time 窗口将包括在系统时钟指示整个小时之间到达特定操作的所有事件。

例如,如果应用程序在上午 9:15 开始运行,则第一个每小时 Processing Time 窗口将包括在上午 9:15 到上午 10:00 之间处理的事件,下一个窗口将包括在上午 10:00 到 11:00 之间处理的事件。

Processing Time 是最简单的 “Time” 概念,不需要流和机器之间的协调,它提供了最好的性能和最低的延迟。但是,在分布式和异步的环境下,Processing Time 不能提供确定性,因为它容易受到事件到达系统的速度(例如从消息队列)、事件在系统内操作流动的速度以及中断的影响。

Event Time

Event Time 是事件发生的时间,一般就是数据本身携带的时间。这个时间通常是在事件到达 Flink 之前就确定的,并且可以从每个事件中获取到事件时间戳。在 Event Time 中,时间取决于数据,而跟其他没什么关系。Event Time 程序必须指定如何生成 Event Time 水印,这是表示 Event Time 进度的机制。

完美的说,无论事件什么时候到达或者其怎么排序,最后处理 Event Time 将产生完全一致和确定的结果。但是,除非事件按照已知顺序(按照事件的时间)到达,否则处理 Event Time 时将会因为要等待一些无序事件而产生一些延迟。由于只能等待一段有限的时间,因此就难以保证处理 Event Time 将产生完全一致和确定的结果。

假设所有数据都已到达, Event Time 操作将按照预期运行,即使在处理无序事件、延迟事件、重新处理历史数据时也会产生正确且一致的结果。 例如,每小时事件时间窗口将包含带有落入该小时的事件时间戳的所有记录,无论它们到达的顺序如何。

请注意,有时当 Event Time 程序实时处理实时数据时,它们将使用一些 Processing Time 操作,以确保它们及时进行。

Ingestion Time

Ingestion Time 是事件进入 Flink 的时间。 在源操作处,每个事件将源的当前时间作为时间戳,并且基于时间的操作(如时间窗口)会利用这个时间戳。

Ingestion Time 在概念上位于 Event Time 和 Processing Time 之间。 与 Processing Time 相比,它稍微贵一些,但结果更可预测。因为 Ingestion Time 使用稳定的时间戳(在源处分配一次),所以对事件的不同窗口操作将引用相同的时间戳,而在 Processing Time 中,每个窗口操作符可以将事件分配给不同的窗口(基于机器系统时间和到达延迟)。

与 Event Time 相比,Ingestion Time 程序无法处理任何无序事件或延迟数据,但程序不必指定如何生成水印。

在 Flink 中,,Ingestion Time 与 Event Time 非常相似,但 Ingestion Time 具有自动分配时间戳和自动生成水印功能。

说了这么多概念比较干涩,下面直接看图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13qBumxc-1626944190943)(picture/Flink中的Time.jpg)]

  • Event Time:事件产生的时间,即数据产生时自带时间戳,例如‘2016/06/17 11:04:00.960’
  • Ingestion Time:数据进入到Flink的时间,即数据进入source operator时获取时间戳
  • Processing Time:系统时间,与数据本身的时间戳无关,即在window窗口内计算完成的时间(默认的Time)

设定时间特性

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);

// 其他
// env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime);
// env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

DataStream<MyEvent> stream = env.addSource(new FlinkKafkaConsumer09<MyEvent>(topic, schema, props));

stream
    .keyBy( (event) -> event.getUser() )
    .timeWindow(Time.hours(1))
    .reduce( (a, b) -> a.add(b) )
    .addSink(...); 
1.Event Time 和 Watermarks

支持 Event Time 的流处理器需要一种方法来衡量 Event Time 的进度。 例如,当 Event Time 超过一小时结束时,需要通知构建每小时窗口的窗口操作符,以便操作员可以关闭正在进行的窗口。

Event Time 可以独立于 Processing Time 进行。 例如,在一个程序中,操作员的当前 Event Time 可能略微落后于 Processing Time (考虑到接收事件的延迟),而两者都以相同的速度进行。另一方面,另一个流程序可能只需要几秒钟的时间就可以处理完 Kafka Topic 中数周的 Event Time 数据。

Flink 中用于衡量 Event Time 进度的机制是 Watermarks。 Watermarks 作为数据流的一部分流动并带有时间戳 t。 Watermark(t)声明 Event Time 已到达该流中的时间 t,这意味着流中不应再有具有时间戳 t’<= t 的元素(即时间戳大于或等于水印的事件)

下图显示了带有(逻辑)时间戳和内联水印的事件流。在本例中,事件是按顺序排列的(相对于它们的时间戳),这意味着水印只是流中的周期性标记。

2. 时间戳和水位线背后的机制
2.1 Watermarks是干啥的

out-of-order/late element实时系统中,由于各种原因造成的延时,造成某些消息发到flink的时间延时于事件产生的时间。如果基于event time构建window,但是对于late element,我们又不能无限期的等下去,必须要有个机制来保证一个特定的时间后,必须触发window去进行计算了。这个特别的机制,就是watermark。
Watermarks(水位线)就是来处理这种问题的机制

1.参考google的DataFlow。
2.是event time处理进度的标志。
3.表示比watermark更早(更老)的事件都已经到达(没有比水位线更低的数据 )。
4.基于watermark来进行窗口触发计算的判断。
2.2 有序流中Watermarks

在某些情况下,基于Event Time的数据流是有续的(相对event time)。在有序流中,watermark就是一个简单的周期性标记。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9RbUbu3b-1626944190945)(picture/有序流中watermarks.png)]

2.3 乱序流中Watermarks

在更多场景下,基于Event Time的数据流是无续的(相对event time)。在无序流中,watermark至关重要,她告诉operator比watermark更早(更老/时间戳更小)的事件已经到达, operator可以将内部事件时间提前到watermark的时间戳(可以触发window计算啦)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zDzx5gtZ-1626944190946)(picture/乱序流中Watermarks.png)]

上图可以类比银行或者医院的排号来理解。

2.4并行流中的Watermarks

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8npvBVFy-1626944190948)(picture/并行流中watermark.png)]

Flink的窗口操作

滚动窗口

滚动窗口分配器将每个元素分配的一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠。例如:如果你指定了一个5分钟大小的滚动窗口,当前窗口将被评估并将按下图说明每5分钟创建一个新的窗口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZzsW76ks-1626944190949)(picture/tumbling-windows.svg)]

DataStream<T> input = ...;

// tumbling event-time windows
input
    .keyBy(<key selector>)
    .window(TumblingEventTimeWindows.of(Time.seconds(5)))
    .<windowed transformation>(<window function>);

// tumbling processing-time windows (滚动处理时间窗口)
input
    .keyBy(<key selector>)
    .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
    .<windowed transformation>(<window function>);

// daily tumbling event-time windows offset by -8 hours.(每日偏移8小时的滚动事件时间窗口)
input
    .keyBy(<key selector>)
    .window(TumblingEventTimeWindows.of(Time.days(1), Time.hours(-8)))
    .<windowed transformation>(<window function>);

时间间隔可以通过使用一个指定Time.milliseconds(x),Time.seconds(x), Time.minutes(x),等等。

如上一个示例所示,翻滚窗口分配器还采用可选offset 参数,可用于更改窗口的对齐方式。例如,如果没有偏移每小时翻滚窗口划时代对齐,这是你会得到如窗口 1:00:00.000 - 1:59:59.999,2:00:00.000 - 2:59:59.999等等。如果你想改变它,你可以给出一个偏移量。随着15分钟的偏移量,你会,例如,拿 1:15:00.000 - 2:14:59.999,2:15:00.000 - 3:14:59.999等一个重要的用例的偏移是窗口调整到比UTC-0时区等。例如,在中国,您必须指定偏移量Time.hours(-8)。

滑动窗口

该滑动窗口分配器分配元件以固定长度的窗口。与翻滚窗口分配器类似,窗口大小由窗口大小参数配置。附加的窗口滑动参数控制滑动窗口的启动频率。因此,如果幻灯片小于窗口大小,则滑动窗口可以重叠。在这种情况下,数据元被分配给多个窗口。

例如,您可以将大小为10分钟的窗口滑动5分钟。有了这个,你每隔5分钟就会得到一个窗口,其中包含过去10分钟内到达的事件,如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5aaIj5im-1626944190949)(picture/sliding-windows.svg)]

DataStream<T> input = ...;

// sliding event-time windows
input
    .keyBy(<key selector>)
    .window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)))
    .<windowed transformation>(<window function>);

// sliding processing-time windows
input
    .keyBy(<key selector>)
    .window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)))
    .<windowed transformation>(<window function>);

// sliding processing-time windows offset by -8 hours
input
    .keyBy(<key selector>)
    .window(SlidingProcessingTimeWindows.of(Time.hours(12), Time.hours(1), Time.hours(-8)))
    .<windowed transformation>(<window function>);

会话窗口(Session Windows)

session窗口分配器通过session活动来对元素进行分组,session窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况。相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个session窗口通过一个session间隔来配置,这个session间隔定义了非活跃周期的长度。当这个非活跃周期产生,那么当前的session将关闭并且后续的元素将被分配到新的session窗口中去。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dLsyFdLz-1626944190951)(picture/session-windows.svg)]

DataStream<T> input = ...;

// event-time session windows with static gap
input
    .keyBy(<key selector>)
    .window(EventTimeSessionWindows.withGap(Time.minutes(10)))
    .<windowed transformation>(<window function>);
    
// event-time session windows with dynamic gap
input
    .keyBy(<key selector>)
    .window(EventTimeSessionWindows.withDynamicGap((element) -> {
        // determine and return session gap
    }))
    .<windowed transformation>(<window function>);

// processing-time session windows with static gap
input
    .keyBy(<key selector>)
    .window(ProcessingTimeSessionWindows.withGap(Time.minutes(10)))
    .<windowed transformation>(<window function>);
    
// processing-time session windows with dynamic gap
input
    .keyBy(<key selector>)
    .window(ProcessingTimeSessionWindows.withDynamicGap((element) -> {
        // determine and return session gap
    }))
    .<windowed transformation>(<window function>);

静态间隙可以通过使用中的一个来指定Time.milliseconds(x),Time.seconds(x), Time.minutes(x),等。

通过实现SessionWindowTimeGapExtractor接口指定动态间隙。

注意由于会话窗口没有固定的开始和结束,因此它们的评估方式与翻滚和滑动窗口不同。在内部,会话窗口算子为每个到达的记录创建一个新窗口,如果它们彼此之间的距离比定义的间隙更接近,则将窗口合并在一起。为了可合并的,会话窗口 算子操作者需要一个合并触发器和一个合并 的窗函数,如ReduceFunction,AggregateFunction,或ProcessWindowFunction (FoldFunction不能合并。)

全局窗口(Global Windows)

全局窗口分配器将所有具有相同key的元素分配到同一个全局窗口中,这个窗口模式仅适用于用户还需自定义触发器的情况。否则,由于全局窗口没有一个自然的结尾,无法执行元素的聚合,将不会有计算被执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fozQLkbu-1626944190952)(picture/non-windowed.svg)]
下面的代码片段展示了如何使用全局窗口:

DataStream<T> input = ...;

input
    .keyBy(<key selector>)
    .window(GlobalWindows.create())
    .<windowed transformation>(<window function>);

窗口函数(Window Functions)

定义完窗口分配器后,我们还需要为每一个窗口指定我们需要执行的计算,这是窗口的责任,当系统决定一个窗口已经准备好执行之后,这个窗口函数将被用来处理窗口中的每一个元素(可能是分组的)。请参考:https://ci.apache.org/projects/flink/flink-docs-release-1.3/dev/windows.html#triggers 来了解当一个窗口准备好之后,Flink是如何决定的。
  window函数可以是ReduceFunction, FoldFunction 或者 WindowFunction 中的一个。前面两个更高效一些(),因为在每个窗口中增量地对每一个到达的元素执行聚合操作。一个 WindowFunction 可以获取一个窗口中的所有元素的一个迭代以及哪个元素属于哪个窗口的额外元信息。

ReduceFunction

A ReduceFunction指定如何组合输入中的两个数据元以生成相同类型的输出数据元。Flink使用a ReduceFunction来递增地聚合窗口的数据元。

A ReduceFunction可以像这样定义和使用:

DataStream<Tuple2<String, Long>> input = ...;

input
    .keyBy(<key selector>)
    .window(<window assigner>)
    .reduce(new ReduceFunction<Tuple2<String, Long>> {
      public Tuple2<String, Long> reduce(Tuple2<String, Long> v1, Tuple2<String, Long> v2) {
        return new Tuple2<>(v1.f0, v1.f1 + v2.f1);
      }
    });

聚合函数

An AggregateFunction是一个通用版本,ReduceFunction它有三种类型:输入类型(IN),累加器类型(ACC)和输出类型(OUT)。输入类型是输入流中数据元的类型,并且AggregateFunction具有将一个输入数据元添加到累加器的方法。该接口还具有用于创建初始累加器的方法,用于将两个累加器合并到一个累加器中以及用于OUT从累加器提取输出(类型)。我们将在下面的示例中看到它的工作原理。

与之相同ReduceFunction,Flink将在窗口到达时递增地聚合窗口的输入数据元。

/**
 * The accumulator is used to keep a running sum and a count. The {@code getResult} method
 * computes the average.
 */
private static class AverageAggregate
    implements AggregateFunction<Tuple2<String, Long>, Tuple2<Long, Long>, Double> {
  @Override
  public Tuple2<Long, Long> createAccumulator() {
    return new Tuple2<>(0L, 0L);
  }

  @Override
  public Tuple2<Long, Long> add(Tuple2<String, Long> value, Tuple2<Long, Long> accumulator) {
    return new Tuple2<>(accumulator.f0 + value.f1, accumulator.f1 + 1L);
  }

  @Override
  public Double getResult(Tuple2<Long, Long> accumulator) {
    return ((double) accumulator.f0) / accumulator.f1;
  }

  @Override
  public Tuple2<Long, Long> merge(Tuple2<Long, Long> a, Tuple2<Long, Long> b) {
    return new Tuple2<>(a.f0 + b.f0, a.f1 + b.f1);
  }
}

DataStream<Tuple2<String, Long>> input = ...;

input
    .keyBy(<key selector>)
    .window(<window assigner>)
    .aggregate(new AverageAggregate());

ProcessWindowFunction

ProcessWindowFunction获取包含窗口的所有数据元的Iterable,以及可访问时间和状态信息的Context对象,这使其能够提供比其他窗口函数更多的灵活性。这是以性能和资源消耗为代价的,因为数据元不能以递增方式聚合,而是需要在内部进行缓冲,直到窗口被认为已准备好进行处理。

ES写入

FailureHandler 失败处理器
12:08:07.326 [I/O dispatcher 13] ERROR o.a.f.s.c.e.ElasticsearchSinkBase - Failed Elasticsearch item request: ElasticsearchException[Elasticsearch exception [type=es_rejected_execution_exception, reason=rejected execution of org.elasticsearch.transport.TransportService$7@566c9379 on EsThreadPoolExecutor[name = node-1/write, queue capacity = 200, org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor@f00b373[Running, pool size = 4, active threads = 4, queued tasks = 200, completed tasks = 6277]]]]

写入 ES 的时候会有这些情况会导致写入 ES 失败:

1、ES 集群队列满了,报如下错误
我这里如果配置的 bulk flush size 并发 sink 数量 这个值如果大于这个 queue capacity ,那么就很容易导致出现这种因为 es 队列满了而写入失败。
2、ES 集群某个节点挂了

这个就不用说了,肯定写入失败的。跟过源码可以发现 RestClient 类里的 performRequestAsync 方法一开始会随机的从集群中的某个节点进行写入数据,如果这台机器掉线,会进行重试在其他的机器上写入,那么当时写入的这台机器的请求就需要进行失败重试,否则就会把数据丢失!

3、ES 集群某个节点的磁盘满了

这里说的磁盘满了,并不是磁盘真的就没有一点剩余空间的,是 es 会在写入的时候检查磁盘的使用情况,在 85% 的时候会打印日志警告。
目前生产上当磁盘使用率达到75%时会告警

** 查看当前线程组状态 **

curl -XGET 'http://localhost:9200/_nodes/stats?pretty'

"thread_pool" : {
    "bulk" : {
      "threads" : 32,
      "queue" : 0,
      "active" : 0,
      "rejected" : 0,
      "largest" : 32,
      "completed" : 659997
    },
"index" : {
      "threads" : 2,
      "queue" : 0,
      "active" : 0,
      "rejected" : 0,
      "largest" : 2,
      "completed" : 2
    }
 

上面截取了部分线程池的配置,其中,最需要关注的是rejected。当某个线程池active==threads时,表示所有线程都在忙,那么后续新的请求就会进入queue中,即queue>0,一旦queue大小超出限制,如bulk的queue默认50,那么elasticsearch进程将拒绝请求(碰到bulk HTTP状态码429),相应的拒绝次数就会累加到rejected中。

解决方法是

1、记录失败的请求并重发

2、减少并发写的进程个数,同时加大每次bulk请求的size

查看节点配置

GET _nodes/stats?pretty

通过ES提供的HTTP接口:

查看线程池配置
GET /ES地址/_cat/thread_pool?v
// 注:?v表示显示表头(header),也可指定具体显示哪里列
// 例:/_cat/thread_pool?v&h=host,bulk.active,bulk.rejected,bulk.queue,bulk.queueSize,bulk.size,bulk.min,bulk.max,search.active,search.rejected,search.queue,search.queueSize

查看各节点线程池配置
GET /ES地址/_nodes/thread_pool/

查看集群配置
GET /ES地址/_cat/_cluster/settings

修改集群配置
PUT /ES地址/_cat/_cluster/settings

线程池与队列

一个Elasticsearch节点会有多个线程池,但重要的是下面四个:

  • 索引(index):主要是索引数据和删除数据操作(默认是cached类型)
  • 搜索(search):主要是获取,统计和搜索操作(默认是cached类型)
  • 批量操作(bulk):主要是对索引的批量操作(默认是cached类型)
  • 更新(refresh):主要是更新操作(默认是cached类型)
generic
For generic operations (e.g., background node discovery). Thread pool type is scaling.
index
For index/delete operations. Thread pool type is fixed with a size of # of available processors, queue_size of 200. The maximum size for this pool is 1 + # of available processors.
search
For count/search/suggest operations. Thread pool type is fixed with a size of int((# of available_processors * 3) / 2) + 1, queue_size of 1000.
get
For get operations. Thread pool type is fixed with a size of # of available processors, queue_size of 1000.
bulk
For bulk operations. Thread pool type is fixed with a size of # of available processors, queue_size of 200. The maximum size for this pool is 1 + # of available processors.
snapshot
For snapshot/restore operations. Thread pool type is scaling with a keep-alive of 5m and a max of min(5, (# of available processors)/2).
warmer
For segment warm-up operations. Thread pool type is scaling with a keep-alive of 5m and a max of min(5, (# of available processors)/2).
refresh
For refresh operations. Thread pool type is scaling with a keep-alive of 5m and a max of min(10, (# of available processors)/2).
listener
Mainly for java client executing of action when listener threaded is set to true. Thread pool type is scaling with a default max of min(10, (# of available processors)/2).
Flink部署
FlinK on Yarn
  1. Flink on Yarn – Per Job
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DxRiFkvF-1626944190964)(picture/FlinkOnYarnJob.png)]
  2. Flink on Yarn – Session
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gvcxNgJo-1626944190965)(picture/FlinkOnYarnSession.png)]
Flink on Kubernets

Flink on Kubernetes —— 架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EJkf2S3q-1626944190966)(picture/FlinkOnk8s.png)]
同一个镜像根据不同的启动命令运行Master或Worker

Flink on Kubernetes – JobManager
  • JobManager Deployment
    • 保证1个副本的container运行JobManager
    • 应用标签,例如 flink-jobmanager
  • JobManager Service
    • 通过service name和port暴露JobManager服务
    • 通过标签选择对应的pods,例如flink-jobmanager
Flink on Kubernetes – TaskManager
  • TaskManager Deployment
    • 保证n个副本的container运行TaskManager
    • 应用标签,例如flink-taskmanager
  • Flink conf ConfigMap
    • flink-conf.yaml
Flink on Kubernetes – 交互
  • Service:通过标签(label selector)找到job manager
  • Deployment:保证n个副本的container运行JM/TM
  • ConfigMap: 在每个pod上通过挂在/ect/flink目录包含flink-conf.yaml
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKPh0BJY-1626944190967)(picture/FlinkOnK8s交互.png)]

配置文件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zC3lc4ng-1626944190968)(picture/jobmanager-deployment.yarml.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-idjNGUAV-1626944190969)(picture/jobmanager-deployment.yarml.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uRSyGhVp-1626944190969)(picture/jobmanager-service.yarml.png)]

Flink容错

Flink can guarantee exactly-once state updates to user-defined state only when the source participates in the snapshotting mechanism. The following table lists the state update guarantees of Flink coupled with the bundled connectors.

Elasticsearch Connector

Elasticsearch Sinks and Fault Tolerance

With Flink’s checkpointing enabled, the Flink Elasticsearch Sink guarantees at-least-once delivery of action requests to Elasticsearch clusters. It does so by waiting for all pending action requests in the BulkProcessor at the time of checkpoints. This effectively assures that all requests before the checkpoint was triggered have been successfully acknowledged by Elasticsearch, before proceeding to process more records sent to the sink.

To use fault tolerant Elasticsearch Sinks, checkpointing of the topology needs to be enabled at the execution environment:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(5000); // checkpoint every 5000 msecs
Handling Failing Elasticsearch Requests

Elasticsearch action requests may fail due to a variety of reasons, including temporarily saturated node queue capacity or malformed documents to be indexed. The Flink Elasticsearch Sink allows the user to specify how request failures are handled, by simply implementing an ActionRequestFailureHandler and providing it to the constructor.

 

DataStream<String> input = ...;

input.addSink(new ElasticsearchSink<>(
    config, transportAddresses,
    new ElasticsearchSinkFunction<String>() {...},
    new ActionRequestFailureHandler() {
        @Override
        void onFailure(ActionRequest action,
                Throwable failure,
                int restStatusCode,
                RequestIndexer indexer) throw Throwable {

            if (ExceptionUtils.findThrowable(failure, EsRejectedExecutionException.class).isPresent()) {
                // full queue; re-add document for indexing
                indexer.add(action);
            } else if (ExceptionUtils.findThrowable(failure, ElasticsearchParseException.class).isPresent()) {
                // malformed document; simply drop request without failing sink
            } else {
                // for all other failures, fail the sink
                // here the failure is simply rethrown, but users can also choose to throw custom exceptions
                throw failure;
            }
        }
}));

IMPORTANT: Re-adding requests back to the internal BulkProcessor on failures will lead to longer checkpoints, as the sink will also need to wait for the re-added requests to be flushed when checkpointing. For example, when using RetryRejectedExecutionFailureHandler, checkpoints will need to wait until Elasticsearch node queues have enough capacity for all the pending requests. This also means that if re-added requests never succeed, the checkpoint will never finish.

配置 Flink 进程的内存

Flink JVM 进程的进程总内存(Total Process Memory)包含了由 Flink 应用使用的内存(Flink 总内存)以及由运行 Flink 的 JVM 使用的内存。 Flink 总内存(Total Flink Memory)包括 JVM 堆内存(Heap Memory)和堆外内存(Off-Heap Memory)。 其中堆外内存包括直接内存(Direct Memory)和本地内存(Native Memory)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jx8ZcFCW-1626944190970)(picture/process_mem_model.svg)]
配置 Flink 进程内存最简单的方法是指定以下两个配置项中的任意一个:

配置项TaskManager 配置参数JobManager 配置参数
Flink总内存taskmanager.memory.flink.sizejobmanager.memory.flink.size
进程总内存taskmanager.memory.process.sizejobmanager.memory.process.size
# JobManagerJVM heap 内存大小(任务提交阶段可再设置,优先级高于配置文件)
jobmanager.heap.mb: 1024
# TaskManagerJVM heap 内存大小(任务提交阶段可再设置,优先级高于配置文件)
taskmanager.heap.mb: 2048
#每个TaskManager提供的任务slots数量大小(任务提交阶段可再设置,优先级高于配置文件)
taskmanager.numberOfTaskSlots: 1
#Flink任务默认并行度(一般情况下如果是Kafka,按照Kafka分区数即可,p=slot*tm)
parallelism.default: 1
#用于存储和检查点状态的存储类型:filesystem,hdfs,rocksdb
state.backend: rocksdb

调优指南

  1. 独立部署模式(Standalone Deployment)下的内存配置
    独立部署模式下,我们通常更关注 Flink 应用本身使用的内存大小。 建议配置 Flink 总内存(taskmanager.memory.flink.size 或者 jobmanager.memory.flink.size)或其组成部分。 此外,如果出现 Metaspace 不足的问题,可以调整 JVM Metaspace 的大小。

这种情况下通常无需配置进程总内存,因为不管是 Flink 还是部署环境都不会对 JVM 开销 进行限制,它只与机器的物理资源相关
2. 容器(Container)的内存配置
在容器化部署模式(Containerized Deployment)下(Kubernetes、Yarn 或 Mesos),建议配置进程总内存(taskmanager.memory.process.size 或者 jobmanager.memory.process.size)。 该配置参数用于指定分配给 Flink JVM 进程的总内存,也就是需要申请的容器大小。

提示 如果配置了 Flink 总内存,Flink 会自动加上 JVM 相关的内存部分,根据推算出的进程总内存大小申请容器

提示 不建议同时设置进程总内存和 Flink 总内存。 这可能会造成内存配置冲突,从而导致部署失败。 额外配置其他内存部分时,同样需要注意可能产生的配置冲突。

Flink K8s容器化部署问题:

  1. JobManager重启问题:
日志:org.apache.flink.util.FlinkException: JobManager responsible for 774762516442166d00b8ddb3f27ad81f lost the leadership.
at org.apache.flink.runtime.taskexecutor.TaskExecutor$JobManagerHeartbeatListener.notifyHeartbeatTimeout(TaskExecutor.java:1797)
...
Caused by: java.util.concurrent.TimeoutException: The heartbeat of JobManager with id 4ff559d2964a97837fcc510a6da118a8 timed out.

2020-07-29 10:58:18 at org.apache.flink.runtime.taskexecutor.TaskExecutor$JobManagerHeartbeatListener.notifyHeartbeatTimeout(TaskExecutor.java:1798)
2020-07-29 10:58:18 … 26 more

解决方法:增大heartbeat.timeout时间
heartbeat.timeout
50000 Timeout for requesting and receiving heartbeat for both sender and receiver sides.

  1. 覆盖log4j.properties无效,设置为debug仍然会有没有debug日志输出
    解决方法:
    改成log4j-console.properties,本地使用的是log4j.properties,容器平台是默认使用的是log4j-console.properties.
    2020-07-23 20:03:22 2020-07-23 12:03:22,352 INFO org.apache.flink.runtime.taskexecutor.TaskManagerRunner - -Dlog4j.configuration=file:/opt/deployments/flink-1.9.1/conf/log4j-console.properties
  2. 生产ES异常导致taskmanager重启:
    Caused by: java.lang.RuntimeException: An error occurred in ElasticsearchSink.
    2020-07-29 19:29:52 at org.apache.flink.streaming.connectors.elasticsearch.ElasticsearchSinkBase.checkErrorAndRethrow(ElasticsearchSinkBase.java:381)
    2020-07-29 19:29:52 at org.apache.flink.streaming.connectors.elasticsearch.ElasticsearchSinkBase.checkAsyncErrorsAndRequests(ElasticsearchSinkBase.java:386)
    2020-07-29 19:29:52 at org.apache.flink.streaming.connectors.elasticsearch.ElasticsearchSinkBase.invoke(ElasticsearchSinkBase.java:307)
    2020-07-29 19:29:52 at org.apache.flink.streaming.api.operators.StreamSink.processElement(StreamSink.java:56)
    2020-07-29 19:29:52 at org.apache.flink.streaming.runtime.tasks.OperatorChain C o p y i n g C h a i n i n g O u t p u t . p u s h T o O p e r a t o r ( O p e r a t o r C h a i n . j a v a : 637 ) 2020 − 07 − 2919 : 29 : 52...30 m o r e 2020 − 07 − 2919 : 29 : 52 C a u s e d b y : j a v a . n e t . C o n n e c t E x c e p t i o n 2020 − 07 − 2919 : 29 : 52 a t o r g . a p a c h e . h t t p . n i o . p o o l . R o u t e S p e c i f i c P o o l . t i m e o u t ( R o u t e S p e c i f i c P o o l . j a v a : 168 ) 2020 − 07 − 2919 : 29 : 52 a t o r g . a p a c h e . h t t p . n i o . p o o l . A b s t r a c t N I O C o n n P o o l . r e q u e s t T i m e o u t ( A b s t r a c t N I O C o n n P o o l . j a v a : 561 ) 2020 − 07 − 2919 : 29 : 52 a t o r g . a p a c h e . h t t p . n i o . p o o l . A b s t r a c t N I O C o n n P o o l CopyingChainingOutput.pushToOperator(OperatorChain.java:637) 2020-07-29 19:29:52 ... 30 more 2020-07-29 19:29:52 Caused by: java.net.ConnectException 2020-07-29 19:29:52 at org.apache.http.nio.pool.RouteSpecificPool.timeout(RouteSpecificPool.java:168) 2020-07-29 19:29:52 at org.apache.http.nio.pool.AbstractNIOConnPool.requestTimeout(AbstractNIOConnPool.java:561) 2020-07-29 19:29:52 at org.apache.http.nio.pool.AbstractNIOConnPool CopyingChainingOutput.pushToOperator(OperatorChain.java:637)2020072919:29:52...30more2020072919:29:52Causedby:java.net.ConnectException2020072919:29:52atorg.apache.http.nio.pool.RouteSpecificPool.timeout(RouteSpecificPool.java:168)2020072919:29:52atorg.apache.http.nio.pool.AbstractNIOConnPool.requestTimeout(AbstractNIOConnPool.java:561)2020072919:29:52atorg.apache.http.nio.pool.AbstractNIOConnPoolInternalSessionRequestCallback.timeout(AbstractNIOConnPool.java:822)
    2020-07-29 19:29:52 at org.apache.http.impl.nio.reactor.SessionRequestImpl.timeout(SessionRequestImpl.java:183)
    2020-07-29 19:29:52 at org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.processTimeouts(DefaultConnectingIOReactor.java:210)
    2020-07-29 19:29:52 at org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.processEvents(DefaultConnectingIOReactor.java:155)
    2020-07-29 19:29:52 at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor.execute(AbstractMultiworkerIOReactor.java:348)
    2020-07-29 19:29:52 at org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager.execute(PoolingNHttpClientConnectionManager.java:192)
    2020-07-29 19:29:52 at org.apache.http.impl.nio.client.CloseableHttpAsyncClientBase$1.run(CloseableHttpAsyncClientBase.java:64)
    2020-07-29 19:29:52 at java.lang.Thread.run(Thread.java:745)
    2020/07/29 20:25:21 (80249635/王荣荣):
    ERROR org.apache.flink.streaming.connectors.elasticsearch.ElasticsearchSinkBase - Failed Elasticsearch bulk request: null
    2020-07-29T00:11:04.502Z appmonitorservice-task-66764957f8-gkthf Caused by: java.lang.RuntimeException: An error occurred in ElasticsearchSink.
    2020-07-29T00:11:04.504Z appmonitorservice-task-66764957f8-gkthf 2020-07-28 16:11:03,862 ERROR org.apache.flink.streaming.runtime.tasks.StreamTask - Error during disposal of stream operator.
    解决方法:启用checkpoint,增加ES异常处理

Flink源码

1. 本地模式下的execute方法

这行代码主要做了以下事情:

  • 生产StreamGraph。代表程序的拓扑结构,是从用户代码直接生成的图。
  • 生成JobGraph。这个图是交给flink去生成task的图。
  • 生成一系列配置
  • 将JobGraph和配置交给flink集群去运行。如果不是本地运行的话,还会把jar文件通过网络发给其他节点。
  • 以本地模式运行的话,可以看到运行过程,如启动性能度量、web模块、JobManager、
    ResourceManager、taskManager等等
  • 启动任务。值得一提的是在启动任务之前,先启动了一个用户类加载器,这个类加载器可
    以用来做一些在运行时动态加载类的工作。

2. DataSource源码

The Flink Kafka connector ingests event streams in parallel. Each parallel source task can read from one or more partitions. A task tracks for each partition its current reading offset and includes it into its checkpoint data.When recovering from a failure, the offsets are restored and the source instance continues reading from the checkpointed offset. The Flink Kafka connector does not rely on Kafka’s own offset-tracking mechanism, which is based on so-called
consumer groups.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值