Stream Processing with Apache Flink中文版-- 第10章 操作Flink和流式应用程序
流处理应用程序是长时间运行的,它们的工作负载通常是不可预测的。连续运行数月的流作业并不少见,因此其操作需求与短期批处理作业的操作需求非常不同。考虑这样一个场景:您在部署的应用程序中检测到一个bug。如果您的应用程序是批处理作业,那么您可以轻松地在脱机状态下修复错误,然后在当前作业实例完成后重新部署新的应用程序代码。但是,如果您的作业是长时间运行的流作业呢?如何在保证正确性的同时,轻松地应用重构?
如果您正在使用Flink,就没有什么好担心的。Flink将完成所有繁重的工作,因此您可以轻松地监视、操作和重新配置您的作业,而只需付出最小的努力,同时保留精确一次状态语义。在本章中,我们将介绍Flink提供的用于操作和维护连续运行的流应用程序的工具。我们将向您展示如何收集指标和监控您的应用程序,以及当您想要更新应用程序代码或调整应用程序的资源时,如何保持结果一致性。
运行和管理流应用程序
如您所料,维护流应用程序比维护批处理应用程序更具挑战性。虽然流应用程序是有状态的并持续运行,但批处理应用程序是定期执行的。可以在执行之间对批处理应用程序进行重新配置、扩展或更新,这比升级不断读取、处理和发送数据的应用程序要容易得多。
但是,Apache Flink有许多特性可以显著地简化流应用程序的维护。这些特性中的大多数都是基于保存点的。Flink公开以下接口来监视和控制其master进程和worker进程以及应用程序:
-
命令行客户端是用于提交和控制应用程序的工具。
-
REST API是命令行客户端和Web UI使用的底层接口。它可以被用户和脚本访问,并提供对所有系统和应用程序指标以及提交和管理应用程序的端点的访问。
-
Web UI是一个Web接口,它提供关于Flink集群和正在运行的应用程序的详细信息和指标。它还提供了提交和管理应用程序的基本功能。Web UI在“Flink Web UI”中进行了描述。
在本节中,我们将解释保存点的实践层面,并讨论如何使用Flink的命令行客户端和Flink的REST API启动、停止、暂停和恢复、扩展和升级有状态流应用程序。
Savepoints(保存点)
保存点(savepoint)基本上与检查点(checkpoint)相同——它是应用程序状态的一致且完整的快照。然而,检查点和保存点的生命周期是不同的。检查点会自动创建,在出现故障时加载,并通过Flink自动删除(取决于应用程序的配置)。此外,当应用程序被取消时,检查点将被自动删除,除非应用程序显式地启用检查点保留。相反,保存点必须由用户或外部服务手动触发,并且不会被Flink自动删除。
保存点是持久化数据存储中的目录。它由一个子目录和一个二进制元数据文件组成,子目录保存包含所有任务状态的数据文件,而二进制元数据文件包含所有数据文件的绝对路径。因为元数据文件中的路径是绝对的,所以将保存点移动到不同的路径将使其不可用。下面是保存点的结构:
# Savepoint root path
/savepoints/
# Path of a particular savepoint
/savepoints/savepoint-:shortjobid-:savepointid/
# Binary metadata file of a savepoint
/savepoints/savepoint-:shortjobid-:savepointid/_metadata
# Checkpointed operator states
/savepoints/savepoint-:shortjobid-:savepointid/:xxx
使用命令行客户端管理应用程序
Flink的命令行客户端提供启动、停止和管理Flink应用程序的功能。它从./conf/flink-conf.yaml文件中读取配置(参见“系统配置”章节)。可以从Flink安装目录中调用命令./bin/flink。
在没有附加参数的情况下运行时,客户端打印一条帮助消息。
WINDOWS上的命令行客户端
命令行客户端基于bash脚本。因此,它不能与Windows命令行一起工作。用于Windows命令行的./bin/flink.bat脚本只提供非常有限的功能。如果您是Windows用户,我们建议您使用常规的命令行客户端并在WSL或Cygwin上运行它。
启动一个应用程序
你可以使用命令行客户端的run命令,启动一个应用程序:
./bin/flink run ~/myApp.jar
上面的命令从类的main()方法启动应用程序,这个方法在JAR文件的META-INF/MANIFEST的program-class属性中引用,不向应用程序传递任何参数。客户端将JAR文件提交给master进程,master进程将其分发给worker节点。
你可以把参数传递给一个应用程序的main()方法,方法是在命令后面附加参数:
./bin/flink run ~/myApp.jar my-arg1 my-arg2 my-arg3
默认情况下,客户端在提交应用程序后不返回,而是等待应用程序终止。你可以使用-d标志以分离(detached)模式提交申请,如下图所示:
./bin/flink run -d ~/myApp.jar
客户端返回并打印提交的作业的JobID,而不是等待应用程序终止。JobID用于在采取保存点、取消或重新调整应用程序时指定job。你可以使用-p标志指定一个应用程序的默认并行度。
./bin/flink run -p 16 ~/myApp.jar
上面的命令将执行环境的默认并行度设置为16。应用程序的源代码显式指定的并行度设置会覆盖执行环境的默认并行度--—通过调用StreamExecutionEnvironment上的setParallelism()或操作符定义的并行性优先于默认值。
如果您的应用程序JAR文件的清单文件没有指定一个入口类,您可以使用-c参数指定这个类:
./bin/flink run -c my.app.MainClass ~/myApp.jar
此时客户端会启动my.app.MainClass类的main方法。
默认情况下,客户端向./conf/flink-conf.yaml文件指定的Flink master服务器提交一个应用程序(参见“系统配置”中的不同设置)。您可以使用-m标志向特定的master进程提交应用程序:
./bin/flink run -m myMasterHost:9876 ~/myApp.jar
此命令将应用程序提交给运行在主机myMasterHost,端口9876上的master。
注意,如果您第一次启动应用程序,或者没有提供保存点或检查点来初始化状态,则应用程序的状态将为空。在这种情况下,一些有状态操作符运行特殊的逻辑来初始化它们的状态。例如,如果没有可用的恢复读位置,Kafka源需要选择所消费Topic的分区偏移量。
列出运行中的应用程序
对于您希望应用于正在运行的作业的所有操作,您需要提供标识应用程序的JobID。JobID可以从Web UI、REST API或使用命令行客户端获得。客户端可以打印出一个所有运行作业的列表,包括他们的jobid,运行以下命令:
./bin/flink list -r
Waiting for response...
------------------ Running/Restarting Jobs ------------------
- 17.10.2018 21:13:14 :
bc0b2ad61ecd4a615d92ce25390f61ad :
Socket Window WordCount (RUNNING)
-------------------------------------------------------------
在本例中,JobID是bc0b2ad61ecd4a615d92ce25390f61ad。
设置和取消保存点
使用命令行客户端,可以为一个运行的应用程序,设置一个保存点,命令如下:
./bin/flink savepoint <jobId> [savepointPath]
该命令使用提供的JobID为作业触发保存点。如果显式指定保存点路径,则将其存储在提供的目录中。否则,使用flink-conf.yaml文件中配置的默认保存点目录。
触发作业bc0b2ad61ecd4a615d92ce25390f61ad的保存点,并将其存储在目录hdfs:///xxx:50070/savepoints中,我们使用命令行客户端:
./bin/flink savepoint bc0b2ad61ecd4a615d92ce25390f61ad hdfs:///xxx:50070/savepoints
Triggering savepoint for job
bc0b2ad61ecd4a615d92ce25390f61ad.
Waiting for response...
Savepoint completed.
Path: hdfs:///xxx:50070/savepoints/savepoint-bc0b2a-
63cf5d5ccef8
You can resume your program from this savepoint with the run command.
保存点会占用大量空间,不会被Flink自动删除。您需要手动删除它们以释放消耗的存储空间。使用以下命令删除保存点:
./bin/flink savepoint -d <savepointPath>
例如,删除我们前面创建的保存点,调用命令:
./bin/flink savepoint -d hdfs:///xxx:50070/savepoints/savepoint-bc0b2a-63cf5d5ccef8
Disposing savepoint 'hdfs:///xxx:50070/savepoints/savepointbc0b2a-
63cf5d5ccef8'.
Waiting for response...
Savepoint 'hdfs:///xxx:50070/savepoints/savepoint-bc0b2a-
63cf5d5ccef8' disposed.
删除保存点
在下一个检查点或保存点完成之前,你不能删除当前保存点。因为保存点是由系统处理,类似于普通检查点,操作符也接收检查点完成通知,完成保存点,并在保存点上采取操作。例如,事务sinks 在保存点完成时,提交修改到外部系统。为了保证精确一次输出,Flink必须从最新完成检查点或保存点恢复。如果Flink试图从一个被删除的保存点恢复,故障恢复将会失败。一旦另一个检查点(或保存点)完成后,您可以安全地删除一个保存点。
取消运行应用程序
可以通过两种方式取消应用程序:以采用保存点的方式取消,或以不采用保存点的方式取消。要取消正在运行的应用程序而不采用保存点,运行以下命令:
./bin/flink cancel <jobId>
为了在取消正在运行的应用程序之前采取一个保存点,将-s标志添加到cancel命令:
./bin/flink cancel -s [savepointPath] <jobId>
如果不指定保存点路径,则使用./conf/flink-conf.yaml文件中配置的默认保存点目录(参见“系统配置”)。如果在命令中未显式指定保存点文件夹,也无法从配置中获得该文件夹,则该命令将失败。若要使用JobID bc0b2ad61ecd4a615d92ce25390f61ad取消应用程序,并将保存点存储在hdfs:///xxx:50070/savepoints,请运行以下命令:
./bin/flink cancel -s hdfs:///xxx:50070/savepoints d5fdaff43022954f5f02fcd8f25ef855
Cancelling job bc0b2ad61ecd4a615d92ce25390f61ad
with savepoint to hdfs:///xxx:50070/savepoints.
Cancelled job bc0b2ad61ecd4a615d92ce25390f61ad.
Savepoint stored in hdfs:///xxx:50070/savepoints/savepointbc0b2a-d08de07fbb10.
取消应用程序可能会失败
注意,如果采用保存点失败,作业将继续运行。你需要再试一次取消应用程序。
从保存点启动应用程序
从保存点启动应用程序相当简单。你所要做的就是用run命令启动一个应用程序,另外用-s选项提供一个保存点的路径:
./bin/flink run -s <savepointPath> [options] <jobJar> [arguments]
当作业启动时,Flink将保存点的各个状态快照与启动的应用程序的所有状态匹配。这种匹配分两个步骤完成。首先,Flink比较保存点的唯一操作符标识符和应用程序的操作符。其次,它为每个操作符匹配保存点和应用程序的状态标识符(有关详细信息,请参阅“保存点”)。
应该定义唯一的操作符ids
如果您没有使用uid()方法为操作符分配惟一的id,那么Flink将分配默认标识符,这些标识符是依赖于操作符的类型和所有先前操作符的散列值。由于不可能更改保存点中的标识符,所以如果不使用uid()手动分配操作符标识符,那么更新和迭代应用程序的可用选项就会减少。
如前所述,应用程序只能在与保存点兼容的情况下从保存点启动。未修改的应用程序始终可以从其保存点重新启动。但是,如果重新启动的应用程序与执行保存点的应用程序不相同,则需要考虑以下三种情况:
-
如果向应用程序添加新状态或更改有状态操作符的唯一标识符,则Flink将在保存点中找不到相应的状态快照。在这种情况下,新状态初始化为空。
-
如果从应用程序中删除状态或更改有状态操作符的唯一标识符,则保存点中存在无法与应用程序匹配的状态。在这种情况下,Flink不会启动应用程序以避免丢失保存点中的状态。您可以通过在run命令中添加-n选项来禁用此安全检查。
-
如果在应用程序中更改了状态—更改了状态原语或修改了状态的数据类型—则应用程序无法启动。这意味着您不能轻松地在应用程序中演进状态的数据类型,除非您在设计应用程序时从一开始就考虑了状态演进。Flink社区目前正在改进对状态演化的支持。(参见“修改操作符的状态”。)
缩放应用程序
减少或增加应用程序的并行性并不困难。您需要生成一个保存点,取消应用程序,然后从保存点用调整后的并行度重新启动它。应用程序的状态会自动重新分配给更多或更少的并行操作符任务。有关如何缩放不同类型的操作符状态和键控状态的详细信息,请参阅“缩放有状态操作符”。然而,有一些事情需要考虑。
如果您需要精确一次的结果,您应该使用保存点,并使用savepoint和cancel命令停止应用程序。这将阻止另一个检查点在保存点之后完成,保存点将触发精确一次的sinks输出保存点之后的数据。
正如在“设置并行度”中讨论的,可以通过不同的方式指定应用程序及其操作符的并行度。默认情况下,操作符以相关的StreamExecutionEnvironment的默认并行度运行。可以在启动应用程序时指定默认的并行度(例如,使用CLI客户端中的-p参数)。如果应用程序中的操作符的并行性依赖于默认的环境并行度,那么可以通过从相同的JAR文件启动应用程序并指定新的并行性来扩展它。但是,如果您在StreamExecutionEnvironment或某些操作符上对并行度进行了硬编码,那么在提交应用程序执行之前,可能需要调整源代码并重新编译和重新打包应用程序。
如果应用程序的并行度取决于环境的默认并行度,那么Flink提供了一个原子rescale命令,该命令接受一个保存点,取消应用程序,并使用新的默认并行度重新启动它:
./bin/flink modify <jobId> -p <newParallelism>
若要使jobId为bc0b2ad61ecd4a615d92ce25390f61ad的应用程序,并行度扩展为16,请运行以下命令:
./bin/flink modify bc0b2ad61ecd4a615d92ce25390f61ad -p 16
Modify job bc0b2ad61ecd4a615d92ce25390f61ad.
Rescaled job bc0b2ad61ecd4a615d92ce25390f61ad. Its new parallelism is 16.
正如在“缩放有状态操作符”中所描述的,Flink将键控状态分布在所谓的key组的粒度上。因此,有状态操作符的key组的数量决定了它的最大并行度。每个操作符使用setMaxParallelism()方法配置key组的数量。(参见“定义键控状态操作符的最大并行度”。)
使用REST API管理应用程序
用户或脚本可以直接访问REST API,查看关于Flink集群及其应用程序的信息,包括提交的应用程序的指标信息,和控制应用程序。Flink从相同的Web服务器提供REST API和Web UI,Web服务器作为Dispatcher进程的一部分运行。默认情况下,两者都在端口8081上公开。您可以在./conf/flinkconf. yaml文件中配置一个不同的端口。值-1禁用REST API和Web UI。
与REST API交互的一个常用命令行工具是curl。典型的curl REST命令如下:
curl -X <HTTP-Method> [-d <parameters>] http://hostname:port/v1/<REST-point>
v1表示REST API的版本。Flink 1.7公开了API的第一个版本(v1)。假设您正在运行一个本地Flink,它在端口8081上公开了它的REST API,下面的curl命令向/overview REST提交一个GET请求:
curl -X GET http://localhost:8081/v1/overview
该命令返回关于集群的一些基本信息,如Flink版本、正在运行、完成、取消或失败的TaskManagers、slots和jobs的数量:
{
"taskmanagers":2,
"slots-total":8,
"slots-available":6,
"jobs-running":1,
"jobs-finished":2,
"jobs-cancelled":1,
"jobs-failed":0,
"flink-version":"1.7.1",
"flink-commit":"89eafb4"
}
在下面,我们列出并简要描述最重要的REST调用。有关受支持的REST调用的完整列表,请参阅Apache Flink的官方文档。“使用命令行客户端管理应用程序”提供了关于某些操作的更多细节,比如升级或扩展应用程序。
管理和监控FLINK集群
REST API公开端点来查询关于正在运行的集群的信息并将其关闭。表10-1、10-2和10-3显示了获取关于Flink集群的信息的REST请求,比如任slots的数量、运行和完成的作业、JobManager的配置或所有可连接的TaskManagers列表。
表10-1 获取基本的集群信息的REST请求
Request | GET /overview |
---|---|
Response | 关于集群的基本信息如前所示 |
表10-2 获取JobManager配置的REST请求
Request | GET /jobmanager/config |
---|---|
Response | 返回./conf/flinkconf.yaml文件中定义的JobManager配置 |
表10-3 列出所有可连接的任务管理器的REST请求
Request | GET /taskmanagers |
---|---|
Response | 返回所有TaskManagers的列表,包括它们的IDs和基本信息(内存统计信息和连接端口) |
表10-4显示的REST请求,列出了为JobManager收集的所有指标。
表10-4 列出可用的JobManager指标的REST请求
Request | GET /jobmanager/metrics |
---|---|
Response | 返回JobManager可用的指标列表 |
为了检索一个或多个JobManager指标,将带有所有请求指标的get查询参数添加到请求中:
curl -X GET http://hostname:port/v1/jobmanager/metrics?get=metric1,metric2
表10-5显示了REST请求,以列出为TaskManagers收集的所有指标。
表10-5 列出可用的TaskManager指标的REST请求
Request | GET /taskmanagers/<tmId>/metrics |
---|---|
Parameters | tmId:可连接的TaskManager ID |
Response | 返回可用于所选TaskManager的指标列表 |
要检索TaskManager的一个或多个指标,请将get查询参数与所有请求的指标一起添加到请求中:
curl -X GET http://hostname:port/v1/taskmanagers/<tmId>/metrics?get=metric1
还可以使用表10-6所示的REST调用来关闭集群
表10-6 关闭集群的REST请求
Request | DELETE /cluster |
---|---|
Action |
关闭Flink集群。注意,在standalone模式下,只有master进程将被终止,worker进程将继续运行。
管理和监控FLINK应用程序
REST API还可以用于管理和监视Flink应用程序。要启动应用程序,首先需要将应用程序的JAR文件上传到集群。表10-7、10-8和10-9显示了管理这些JAR文件的REST请求。
表10-7 上传JAR文件的REST请求
Request | POST /jars/upload |
---|---|
Parameters | 文件必须作为multipart数据发送 |
Action | 将JAR文件上载到集群 |
Response | 上传的JAR文件的存储位置 |
curl命令上传一个JAR文件:
curl -X POST -H "Expect:" -F "jarfile=@path/to/flink-job.jar" http://hostname:port/v1/jars/upload
表10-8 列出所有上传的JAR文件的REST请求
Request | GET /jars |
---|---|
Response | 所有上传的JAR文件的列表。该列表包括JAR文件的内部ID、原始名称和上传时间。 |
表10-9 删除JAR文件的REST请求
Request | DELETE /jars/<jarId> |
---|---|
Parameters | jarId: JAR文件的内部ID(见表10-8命令) |
Action | 根据ID删除JAR文件 |
使用REST请求从上传的JAR文件启动应用程序,如表10-10所示。
表10-10 启动应用程序的REST请求
Request | POST /jars/<jarId>/run |
---|---|
Parameters | jarId:启动应用程序的JAR文件的ID。可以将其他参数(如作业参数、入口类、默认并行性、保存点路径和allow-nonrestored-state标记)作为JSON对象传递。 |
Action | 使用提供的参数启动JAR文件(和入口类)定义的应用程序。如果提供了保存点路径,则从保存点初始化应用程序状态。 |
Response | 启动的应用程序的jobID |
curl命令启动一个默认并行度为4的应用程序:
curl -d '{"parallelism":"4"}' -X POST http://localhost:8081/v1/jars/43e844ef-382f-45c3-aa2f-00549acd961e_App.jar/run
表10-11、10-12和10-13展示了如何使用REST API管理正在运行的应用程序。
表10-11 列出所有应用程序的REST请求
Request | GET /jobs |
---|---|
Response | 列出所有正在运行的应用程序的作业id,以及最近失败、取消和完成的应用程序的作业id。 |
表10-12 显示应用程序细节的REST请求
Request | GET /jobs/<jobId> |
---|---|
Parameters | jobId: list application命令(表10-11)提供的作业ID |
Response | 基本统计信息,如应用程序的名称、开始时间(和结束时间),以及关于执行任务的信息,包括接收和输出的记录和字节的数量 |
REST API还提供了关于应用程序的以下方面的更详细的信息:
-
应用程序的操作计划
-
应用程序的配置
-
在不同层次收集应用程序的度量标准
-
检查点指标
-
反压指标
-
导致应用程序失败的异常
有关如何访问这些信息的详细信息,请参阅官方文档。
表10-13 取消应用程序的REST请求
Request | PATCH /jobs/<jobId> |
---|---|
Parameters | jobId: list application命令(表10-11)提供的作业ID |
Action | 取消应用程序 |
您还可以通过表10-14中所示的REST请求对正在运行的应用程序生成保存点。
表10-14 生成应用程序保存点的REST请求
Request | POST /jobs/<jobId>/savepoints |
---|---|
Parameters | list应用程序命令提供的作业的ID。此外,您需要提供一个JSON对象,其中包含保存点文件夹的路径和一个标志,该标志指示是否使用保存点终止应用程序。 |
Action | 生成应用程序的保存点 |
Response | 一个请求ID,用于检查保存点触发操作是否成功完成。 |
curl命令在不取消应用程序的情况下触发保存点:
curl -d '{"target-directory":"file:///savepoints", "canceljob":"false"}' -X POST
http://localhost:8081/v1/jobs/e99cdb41b422631c8ee2218caa6af1cc/savepoints
{"request-id":"ebde90836b8b9dc2da90e9e7655f4179"}
取消具有保存点的应用程序可能会失败
只有成功地生成了保存点,取消应用程序的请求才会成功。如果savepoint命令失败,应用程序将继续运行。
检查ID为ebde90836b8b9dc2da90e9e7655f4179的请求是否成功,并检索保存点路径:
curl -X GET
http://localhost:8081/v1/jobs/e99cdb41b422631c8ee2218caa6af1cc/savepoints/ebde90836b8b9dc2da90e9e7655f4179
{"status":{"id":"COMPLETED"}
"operation":{"location":"file:///savepoints/savepoint-e99cdb-34410597dec0"}}
要释放保存点,请使用表10-15中所示的REST请求
表10-15释放一个保存点的REST请求
Request | POST /savepoint-disposal |
---|---|
Parameters | 需要在JSON对象中以参数的形式提供要释放保存点的路径 |
Action | 释放保存点 |
Response | 一个请求ID,用于检查保存点是否已成功释放 |
要使用curl释放保存点,请运行:
curl -d '{"savepoint-path":"file:///savepoints/savepointe99cdb-34410597"}'
-X POST http://localhost:8081/v1/savepoint-disposal
{"request-id":"217a4ffe935ceac2c281bdded76729d6"}
表10-16显示了扩展应用程序的REST请求
表10-16 扩展应用程序的REST请求
Request | PATCH /jobs/<jobID>/rescaling |
---|---|
Parameters | jobID: list application命令提供的作业ID。此外,还需要将应用程序的新并行度作为URL参数提供。 |
Action | 生成一个保存点,取消应用程序,并从保存点以新的默认并行度重新启动应用程序。 |
Response | 一个请求ID,用于检查扩展应用程序请求是否成功 |
扩展一个应用程序的curl到一个新的默认并行度为16,运行:
curl -X PATCH
http://localhost:8081/v1/jobs/129ced9aacf1618ebca0ba81a4b222c6/rescaling?parallelism=16
{"request-id":"39584c2f742c3594776653f27833e3eb"}
应用程序可能不会被扩展
如果触发的保存点失败,应用程序将继续以原来的并行度运行。您可以使用请求ID检查扩展请求的状态。
在容器中绑定和部署应用程序
到目前为止,我们已经解释了如何在运行中的Flink集群上启动应用程序。这就是我们所说的部署应用程序的REST请求风格。在“应用程序部署”一节中,我们简要介绍了另一种选择—库模式,它不需要运行中的Flink集群来提交作业。
在库(library)模式下,应用程序被绑定到一个Docker映像中,该映像还包含所需的Flink二进制文件。映像可以通过两种方式启动——作为JobMaster容器或TaskManager容器。当映像作为JobMaster部署时,容器启动一个Flink master进程,该进程立即获取绑定的应用程序以启动它。TaskManager容器在JobMaster上注册自己,并提供它的处理槽。一旦有足够的插槽可用,JobMaster容器就会部署应用程序以供执行。
略。
控制任务调度
Flink应用程序是通过将操作符并行化为任务,并将这些任务分布到集群中的worker进程中来并行执行的。与许多其他分布式系统一样,Flink应用程序的性能在很大程度上取决于如何调度任务。分配任务的worker进程、任务之间的协同以及分配给worker进程的任务数量,可能会对应用程序的性能产生重大影响。
在“任务执行”中,我们描述了Flink如何将任务分配给slots,以及如何利用任务链来降低本地数据交换的成本。在本节中,我们将讨论如何调整默认行为和控制任务链,以及如何将任务分配到slots,以提高应用程序的性能。
控制任务链接
任务链将两个或多个操作符的并行任务合并到一个由单个线程执行的任务中。合并的任务通过方法调用交换记录,因此基本上没有通信成本。由于任务链提高了大多数应用程序的性能,所以在Flink中默认启用了任务链。
然而,某些应用程序可能无法从任务链接中获益。一个原因是为了在不同的处理slots上执行函数,而打破了昂贵的函数链。你可以通过StreamExecutionEnvironment完全禁用应用程序的任务链:
StreamExecutionEnvironment.disableOperatorChaining()
除了为整个应用程序禁用任务链之外,还可以控制各个操作符的任务链行为。要禁用特定操作符的链接,可以调用它的disableChaining()方法。这将防止操作符的任务被链接到前面和后面的任务(例如10-1)。
val input: DataStream[X] = ...
val result: DataStream[Y] = input.filter(new Filter1()).map(new Map1())
// disable chaining for Map2
.map(new Map2()).disableChaining()
.filter(new Filter2())
示例10-1中的代码产生三个任务—Filter1和Map1的一个链接任务,Map2的一个单独任务,以及一个不允许链接到Map2的Filter2任务。
也可以通过调用它的startNewChain()方法(例如10-2)来使用操作符启动一个新的链。操作符的任务不会链接到前面的任务,但是如果满足链接的要求,则会链接到后面的任务。
val input: DataStream[X] = ...
val result: DataStream[Y] = input
.filter(new Filter1())
.map(new Map1())
// start a new chain for Map2 and Filter2
.map(new Map2()).startNewChain()
.filter(new Filter2())
在示例10-2中,创建了两个链接的任务:一个任务用于Filter1和Map1,另一个任务用于Map2和Filter2。注意,新链接的任务以操作符开始,在我们的示例中startNewChain()方法在该操作符上被称为- map2。
定义Slot-Sharing组
Flink的默认任务调度策略将程序的一个完整分片分配给应用程序的每个操作符的一个任务,并将其分配给单个处理slot。根据应用程序的复杂性和操作符的计算成本,这种默认策略可能会使处理slot超载。Flink手动控制任务分配到slot的机制是slot-sharing groups(slot共享组)。
每个操作符都是一个slot共享组的成员。属于同一slot共享组的操作符的所有任务都由相同的slots处理。在一个slot共享组中,任务被分配到“任务执行”章节中描述的slot中——每个slot最多处理一个成员操作符的任务。因此,一个slot共享组需要尽可能多的处理slot来满足其操作符的最大并行度。不同slotsharing组中的操作符任务不会由相同的slot执行。
默认情况下,每个操作符都位于“默认”slot共享组中。对于每个操作符,您可以使用slotSharingGroup(String)方法显式地指定其slotsharing组。如果输入操作符的所有成员都属于同一组,则操作符将继承其输入操作符的slot共享组。如果输入操作符在不同的组中,则操作符在“默认”组中。示例10-3展示了如何在Flink DataStream应用程序中指定slot-sharing组。
// slot-sharing group "green"
val a: DataStream[A] = env.createInput(...)
.slotSharingGroup("green")
.setParallelism(4)
val b: DataStream[B] = a.map(...)
// slot-sharing group "green" is inherited from a
.setParallelism(4)
// slot-sharing group "yellow"
val c: DataStream[C] = env.createInput(...)
.slotSharingGroup("yellow")
.setParallelism(2)
// slot-sharing group "blue"
val d: DataStream[D] = b.connect(c.broadcast(...)).process(...)
.slotSharingGroup("blue")
.setParallelism(4)
val e = d.addSink()
// slot-sharing group "blue" is inherited from d
.setParallelism(2)
示例10-3中的应用程序由五个操作符、两个源、两个中间操作符和一个sink操作符组成。操作符被分配到三个共享位置组:绿色、黄色和蓝色。图10-1显示了应用程序的JobGraph,以及如何将其任务映射到处理slot。
该应用程序需要10个处理slot。由于分配的操作符的最大并行度,蓝色和绿色的slotsharing组需要各4个插槽。黄色slot共享组需要两个slot。
调优检查点和恢复
运行时,启用容错功能的Flink应用程序定期生成状态的检查点。检查点有可能是一项昂贵的操作,因为需要复制到持久存储中的数据量可能非常大。增加检查点间隔可以减少常规处理期间的容错开销。然而,它还增加了作业从失败中恢复时,需要重新处理的数据量。
Flink提供了两个参数来调优检查点和状态后端。配置这些选项对于确保生产中流式应用程序的可靠和平稳运行非常重要。例如,减少每个检查点的开销可以提高检查点频率,从而加快恢复周期。在本节中,我们将描述用于控制检查点和应用程序恢复的参数。
配置检查点
当为应用程序启用检查点时,必须指定检查点间隔—JobManager将在应用程序源(sources)上启动检查点的间隔。
在StreamExecutionEnvironment上启用检查点:
val env: StreamExecutionEnvironment = ???
// enable checkpointing with an interval of 10 seconds.
env.enableCheckpointing(10000);
CheckpointConfig提供了配置检查点行为的更多选项,可以从StreamExecutionEnvironment获得:
// get the CheckpointConfig from the StreamExecutionEnvironment
val cpConfig: CheckpointConfig = env.getCheckpointConfig
默认情况下,Flink创建检查点来确保精确一次的状态一致性。不过,它也可以配置为至少一次状态一致性:
// set mode to at-least-once
cpConfig.setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE);
根据应用程序的特征、状态的大小、状态后端及其配置,检查点可能需要几分钟。此外,随着时间的推移,状态的大小可能会增加或减少,这可能是由于长时间运行的窗口造成的。因此,检查点花费的时间通常比配置的检查点间隔长。默认情况下,Flink一次只允许一个检查点处于进程中,以避免检查点占用常规处理所需的太多资源。如果——根据配置的检查点间隔——需要启动一个检查点,但是有另一个检查点正在进行中,第二个检查点将被搁置,直到第一个检查点完成。
如果大部分检查点耗时比检查点间隔更长,这种行为可能不是最优的,原因有两个。首先,这意味着应用程序的常规数据处理与并行检查点总是争夺资源。因此,其处理减缓,它可能无法取得足够的速度跟上传入的数据。第二,一个检查点可能被推迟,因为我们需要等待另一个检查点完成,导致较低的检查点间隔,导致更长的恢复过程。Flink提供参数来解决这些情况。
为了确保应用程序可以取得足够的处理速度,可以配置检查点之间的最小暂停。如果将最小暂停配置为30秒,则在完成检查点后的前30秒内不会启动新的检查点。这也意味着有效的检查点间隔至少为30秒,同时最多有一个检查点发生。
// make sure we process at least 30s without checkpointing
cpConfig.setMinPauseBetweenCheckpoints(30000);
在某些情况下,您可能想要确保检查点是在配置的检查点间隔内进行的,即使检查点花费的时间比间隔长。一个例子是检查点花费很长时间,但不消耗很多资源;例如,由于对外部系统的调用具有高延迟的操作。在这种情况下,您可以配置并发检查点的最大数量。
// allow three checkpoint to be in progress at the same time
cpConfig.setMaxConcurrentCheckpoints(3);
请注意
保存点与检查点同时执行。由于检查点操作,Flink不会延迟显式触发保存点。无论有多少检查点正在进行中,都会启动一个保存点。
为了避免长时间运行的检查点,您可以配置一个超时间隔,在此间隔之后,检查点将被取消。默认情况下,检查点在10分钟后取消。
// checkpoints have to complete within five minutes, or are discarded
cpConfig.setCheckpointTimeout(300000);
最后,您可能还需要配置检查点失败时的情况。默认情况下,失败的检查点会导致异常,从而导致应用程序重新启动。您可以禁用此行为,并让应用程序在检查点发生错误时,继续运行。
// do not fail the job on a checkpointing error
cpConfig.setFailOnCheckpointingErrors(false);
启用检查点压缩
Flink支持压缩的检查点和保存点。在Flink 1.7之前,唯一支持的压缩算法是Snappy。你可以启用压缩的检查点和保存点如下:
请注意
注意,增量式RocksDB检查点不支持检查点压缩。
在应用程序停止后保留检查点
检查点的目的是在应用程序失败后恢复它。因此,它们在作业停止运行时进行清理,原因可能是应用程序失败,也可能是显式取消。但是,您还可以启用一个称为外部化检查点的功能,以便在应用程序停止后保留检查点。
// Enable externalized checkpoints
cpConfig.enableExternalizedCheckpoints(
ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
外部化检查点有两种选择:
-
RETAIN_ON_CANCELLATION 在应用程序完全失败和显式取消后保留检查点。
-
DELETE_ON_CANCELLATION 只在应用程序完全失败之后才保留检查点。如果应用程序被显式取消,检查点将被删除。
外部化的检查点不替换保存点。它们使用一种状态后端特定的存储格式,并且不支持扩展。因此,它们足以在应用程序失败后重新启动应用程序,但提供的灵活性不如保存点。一旦应用程序再次运行,您可以生成一个保存点。
配置状态后端
应用程序的状态后端负责维护本地状态、执行检查点和保存点,以及在发生故障后恢复应用程序状态。因此,应用程序状态后端的选择和配置对检查点的性能有很大的影响。在“选择状态后端”章节中更详细地描述了各个状态后端。
应用程序的默认状态后端是MemoryStateBackend。由于它将所有状态保存在内存中,并且检查点完全存储在volatile和jvm大小有限的JobManager堆存储中,因此不建议用于生产环境。但是,它对于本地开发的Flink应用程序非常有用。“检查点和状态后端”章节描述了如何配置Flink集群的默认状态后端。
你也可以显式地选择一个应用程序的状态后端:
val env: StreamExecutionEnvironment = ???
// create and configure state backend of your choice
val stateBackend: StateBackend = ???
// set state backend
env.setStateBackend(stateBackend)
可以使用如下所示的最小设置创建不同的状态后端。MemoryStateBackend不需要任何参数。但是,有一些构造函数使用参数来启用或禁用异步检查点(默认启用)并限制状态大小(默认为5 MB):
// create a MemoryStateBackend
val memBackend = new MemoryStateBackend()
FsStateBackend只需要一个路径来定义检查点的存储位置。也有构造函数变量来启用或禁用异步检查点(默认启用):
// create a FsStateBackend that checkpoints to the /tmp/ckpfolder
val fsBackend = new FsStateBackend("file:///tmp/ckp", true)
RocksDBStateBackend只需要一个路径来定义检查点的存储位置,并使用一个可选参数来启用增量检查点(默认情况下禁用)。RocksDBStateBackend总是异步写入检查点:
// create a RocksDBStateBackend that writes incremental checkpoints
// to the /tmp/ckp folder
val rocksBackend = new RocksDBStateBackend("file:///tmp/ckp",true)
在“检查点和状态后端”章节中,我们讨论了状态后端的配置选项。当然,您也可以在应用程序中配置状态后端,覆盖默认值或集群范围的配置。为此,您必须通过将配置对象传递到状态后端来创建新的后端对象。有关可用配置选项的描述,请参阅“检查点和状态后端”:
// all of Flink's built-in backends are configurable
val backend: ConfigurableStateBackend = ???
// create configuration and set options
val sbConfig = new Configuration()
sbConfig.setBoolean("state.backend.async", true)
sbConfig.setString("state.savepoints.dir", "file:///tmp/svp")
// create a configured copy of the backend
val configuredBackend = backend.configure(sbConfig)
由于RocksDB是一个外部组件,它带来了自己的一组调优参数,也可以针对您的应用程序进行调整。默认情况下,RocksDB针对SSD存储进行了优化,如果状态存储在旋转磁盘上,则不会提供很好的性能。Flink提供了一些预定义的设置,用于改善常见硬件设置的性能。有关可用设置的更多信息,请参见文档。你可以应用预定义的选项RocksDBStateBackend如下:
val backend: RocksDBStateBackend = ???
// set predefined options for spinning disk storage
backend.setPredefinedOptions(PredefinedOptions.SPINNING_DISK_OPTIMIZED)
配置恢复
当检查点应用程序失败时,它将通过调出其任务、恢复其状态(包括源任务的读取偏移量)并继续处理来重新启动。在应用程序重新启动之后,它就处于一个追赶阶段。由于应用程序的源任务被重置为更早的输入位置,所以它将处理在故障之前处理的数据和在应用程序宕机期间积累的数据。
为了能够赶上流,到达其尾部—应用程序必须以比新数据到达更高的速度处理累积的数据。当应用程序追赶数据过程中,处理延迟(输入在实际处理之前可用的时间)会增加。
因此,在重新启动应用程序后,应用程序需要足够的备用资源,以便成功地恢复其常规处理。这意味着应用程序在常规处理期间的资源消耗不应该接近100%。可用于恢复的资源越多,追赶阶段完成的速度就越快,处理延迟恢复正常的速度也就越快。
除了恢复的资源考虑之外,我们还将讨论另外两个与恢复相关的主题:重新启动策略和本地恢复。
重新启动策略
根据导致应用程序崩溃的故障,应用程序可能再次被相同的故障杀死。一个常见的例子是应用程序无法处理无效或损坏的输入数据。在这种情况下,应用程序最终会陷入无限的恢复周期,消耗大量资源,而没有机会恢复到常规处理。Flink提供了三种重启策略来解决这个问题:
-
固定延迟重新启动策略(fixed-delay restart strategy):以固定的次数重新启动应用程序,并在重新启动尝试之前等待已配置的时间。
-
故障率重新启动策略(failure-rate restart strategy):只要不超过可配置的故障率,故障率重新启动策略就会重新启动应用程序。故障率被指定为在一个时间间隔内的最大故障数。例如,您可以配置一个应用程序,只要它在过去十分钟内失败不超过三次,就可以重新启动。
-
不重启策略(no-restart strategy):不重启应用程序,但会立即使其失败。
应用程序的重启策略是通过StreamExecutionEnvironment配置的,如示例10-4所示。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setRestartStrategy(
RestartStrategies.fixedDelayRestart(
5, // number of restart attempts
Time.of(30, TimeUnit.SECONDS) // delay between attempts
)
)
如果没有显式定义重启策略,则使用的默认重启策略:fixed-delay restart strategy,使用Integer.MAX_VALUE重新启动尝试和10秒延迟。
本地恢复
Flink的状态后端(MemoryStateBackend除外)将检查点存储在远程文件系统中。这首先确保了状态被保存和持久化,其次确保在worker节点丢失或应用程序扩展时可以重新分配状态。但是,在恢复期间从远程存储读取状态的效率不是很高。此外,在恢复时,可以在发生故障之前,可以在当前worker上重新启动应用程序。
Flink支持一个称为本地恢复的特性,如果应用程序可以在相同的机器上重新启动,它可以显著加快恢复速度。启用状态后端时,除了将数据写入远程存储系统外,还将检查点数据的副本存储在worker节点的本地磁盘上。当应用程序重新启动时,Flink尝试将相同的任务调度到相同的worker节点。如果成功,任务首先尝试从本地磁盘加载检查点数据。如果出现任何问题,它们将退回到远程存储。
实现了本地恢复,使远程系统中的状态副本成为备用来源。任务只有在远程写入成功时才确认生成检查点。而且,检查点不会因为本地状态副本失败而失败。由于检查点数据被写入两次,所以本地恢复增加了检查点的开销。
可以在flink-conf.yaml文件中启用和配置集群的本地恢复特性,也可以为每个应用程序设置不同的如下状态后端配置:
-
state.backend.local-recovery:此标志启用或禁用本地恢复。默认情况下,本地恢复被禁用。
-
taskmanager.state.local.root-dirs:此参数指定存储本地状态副本的一个或多个本地路径。
请注意
本地恢复只影响键控状态,键控状态总是分区的,通常占状态大小的大部分。操作符状态不会存储在本地,需要从远程存储系统检索。但是,它通常比键控状态小得多。而且,MemoryStateBackend不支持本地恢复,因为它不支持大状态。
监视Flink集群和应用程序
监控流作业对于确保其健康运行和及早发现错误配置、资源不足或意外行为的潜在症状至关重要。特别是当流作业是面向用户的应用程序中的大型数据处理管道或事件驱动服务的一部分时,您可能希望尽可能精确地监视它的性能,并确保它满足延迟、吞吐量、资源利用率等特定目标。
Flink在运行时收集一组预定义的指标,并提供一个框架,允许定义和跟踪自己的指标。
Flink Web UI
要获得Flink集群的概况以及对作业在内部执行情况的了解,最简单的方法是使用Flink的Web UI。您可以通过以下地址访问访问仪表板:
http://<jobmanager-hostname>:8081
在主界面上,您将看到集群配置的概述,包括任务管理器的数量、已配置和可用的任务slots的数量,以及正在运行和已完成的作业。图10-2显示了dashboard主屏幕的一个实例。左边的菜单链接到关于作业和配置参数的更详细的信息,还允许通过上传JAR提交作业。
如果单击正在运行的作业,可以快速查看每个任务或子任务的运行统计信息,如图10-3所示。您可以检查持续时间、处理的字节数量和交换的数据记录,并根据需要聚合每个TaskManager的记录。
如果您单击Task Metrics选项卡,您可以从下拉菜单中选择更多的Metrics,如图10-4所示。其中包括关于任务的更细粒度的统计信息,如缓冲区使用情况、水印和输入/输出速率。
图10-5显示了如何将选择的度量显示为连续更新的图表。
Checkpoints选项卡(图10-3)显示关于以前和当前检查点的统计信息。在概述中,您可以看到有多少检查点被触发、正在进行中、已成功完成或失败。如果单击History视图,可以检索更细粒度的信息,比如状态、触发时间、状态大小和检查点对齐阶段缓冲了多少字节。Summary视图聚合检查点统计信息,并提供所有已完成检查点的最小值、最大值和平均值。最后,在配置下,可以检查检查点的配置属性,比如设置的间隔和超时值。
类似地,Back Pressure选项卡显示每个操作符和子任务的背压统计信息。如果单击一行,将触发回压采样,将看到正在进行的消息:Sampling in progress...大约五秒钟。一旦采样完成,您将在第二列中看到反压力状态。背压任务会显示出HIGH信号;否则,您将看到一个漂亮的绿色OK消息显示。
统计系统
在生产环境中运行Flink等数据处理系统时,必须监视其行为,以便能够发现和诊断性能下降的原因。默认情况下,Flink收集多个系统和应用程序指标。收集每个操作符、TaskManager或JobManager相关指标。在这里,我们将描述一些最常用的度量标准,并向您推荐Flink的文档以获得可用度量标准的完整列表。
类别包括CPU利用率、内存使用,活动线程的数量,垃圾收集统计数据,网络指标,如输入/输出缓冲区排队的数量,整个集群范围的指标或运行jobs数量和可用资源等,job指标包括运行时,重试的次数和检查点信息,I / O统计数据,包括在本地和远程交互的记录数量,水印信息,连接器特有指标等。
注册和使用度量标准
要注册指标,您必须通过调用RuntimeContext上的getMetrics()方法来得到MetricGroup,如示例10-5所示。
class PositiveFilter extends RichFilterFunction[Int] {
override def open(parameters: Configuration): Unit = {
counter = getRuntimeContext
.getMetricGroup
.counter("droppedElements")
}
override def filter(value: Int): Boolean = {
if (value > 0) {
true
}
else {
counter.inc()
false
}
}
}
METRIC GROUPS
Flink指标是通过MetricGroup接口注册和访问的。MetricGroup提供了创建嵌套的、命名的度量层次结构的方法,并提供了注册以下度量类型的方法:
Counter(计数器):
一个org.apache.flink.metrics.Counter度量计数并提供递增和递减的方法。可以在MetricGroup上使用counter(String name, Counter counter)方法注册计数器度量。
Gauge(度量):
度量标准在某个时间点计算任意类型的值。要使用度量(Gauge),您需要实现org.apache.flink.metrics.Gauge接口,在MetricGroup上使用gauge(String name, Gauge gauge)方法注册它。例10-6中的代码展示了WatermarkGauge度量的实现,它公开了当前的水印。
public class WatermarkGauge implements Gauge<Long> {
private long currentWatermark = Long.MIN_VALUE;
public void setCurrentWatermark(long watermark) {
this.currentWatermark = watermark;
}
public Long getValue() {
return currentWatermark;
}
}
以字符串形式报告的指标
度量报告程序将把测量值转换成字符串,因此,如果您使用的类型没有提供toString()实现,那么请确保提供了有意义的toString()实现。
Histogram(直方图):
您可以使用直方图来表示数值数据的分布。Flink的直方图特别适用于报告long类型值的度量。org.apache.flink.metrics.Histogram接口允许收集值,获取收集值的当前计数,并为到目前为止看到的值创建统计信息,例如最小值、最大值、标准差和平均值。
除了创建你自己的直方图实现,Flink还允许你使用DropWizard直方图,通过添加依赖。如下:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-metrics-dropwizard</artifactId>
<version>flink-version</version>
</dependency>
然后,您可以使用DropwizardHistogramWrapper类在Flink程序中注册DropWizard直方图,如示例10-7所示。
// create and register histogram
DropwizardHistogramWrapper histogramWrapper =
new DropwizardHistogramWrapper(
new com.codahale.metrics.Histogram(new SlidingWindowReservoir(500))
)
metricGroup.histogram("myHistogram", histogramWrapper)
// update histogram
histogramWrapper.update(value)
Meter:
您可以使用Meter度量来度量特定事件发生的速率(以事件/秒为单位)。org.apache.flink.metrics.Meter接口提供了一些方法来标记一个或多个事件的发生,获取每秒事件的当前速率,以及在Meter上标记的当前事件数。
与直方图一样,您可以通过在pom.xml中添加flink-metrics-dropwizard依赖项并在DropwizardMeterWrapper类中包装meter来使用DropWizard meters。
范围和格式度量
Flink指标属于某一个范围,可以是系统范围(用于系统提供的指标),也可以是用户范围(用于自定义的用户定义指标)。指标由一个包含三部分的唯一标识符引用:
-
用户在注册度量时指定的名称
-
一个可选的用户范围
-
一个系统范围
例如,指定的度量名称:“myCounter”、用户范围:“MyMetrics”和系统范围“localhost.taskmanager.512”。此时的标识符为:" localhost.taskmanager.512.MyMetrics.myCounter‘。可以更改默认分隔符".",通过设置metrics.scope.delimiter配置选项。
系统范围声明了度量所引用的系统组件以及它应该包含的上下文信息。度量可以限定为JobManager、TaskManager、作业、操作符或任务。通过在flink-conf.yaml中设置相应的度量选项,可以配置度量应该包含哪些上下文信息。我们在表10-17中列出了一些配置选项及其默认值。
范围 | 用于配置的key | 默认值 |
---|---|---|
JobManager | metrics.scope.jm | <host>.jobmanager |
JobManager和job | metrics.scope.jm.job | <host>.jobmanager.<job_name> |
TaskManager | metrics.scope.tm | <host>.taskmanager.<tm_id> |
TaskManager和job | metrics.scope.tm.job | <host>.taskmana ger.<tm_id>.<job_name> |
Task | metrics.scope.task | <host>.taskmanager.<tm_id>.<job_name>.<task_name>.<subtask_index> |
Operator | metrics.scope.operator | <host>.taskmanager.<tm_id>.<job_name>.<operator_name>.<subtask_index> |
配置key值包含常量字符串,如“taskmanager”和尖括号中显示的变量。后者将在运行时用实际值替换。例如,TaskManager指标的默认范围可能创建范围“localhost.taskmanager.512”。其中“localhost”和“512”是参数值。表10-18显示了所有可用来配置度量范围的变量。
范围 | 可用变量 |
---|---|
JobManager: | <host> |
TaskManager: | <host>, <tm_id> |
Job: | <job_id>, <job_name> |
Task: | <task_id>, <task_name>, <task_attempt_id>,<task_attempt_num>, <subtask_index> |
Operator: | <operator_id>, <operator_name>, <subtask_index> |
每个作业的范围标识符必须是唯一的
如果同时运行同一作业的多个副本,则由于字符串冲突,度量可能会变得不准确。为了避免这种风险,您应该确保每个作业的范围标识符是惟一的。这可以通过包含<job_id>来轻松处理。
您还可以通过调用MetricGroup的addGroup()方法来定义度量的用户范围,如示例10-8所示。
counter = getRuntimeContext
.getMetricGroup
.addGroup("MyMetrics")
.counter("myCounter")
访问指标
既然您已经了解了如何注册、定义和分组度量标准,您可能想知道如何从外部系统访问它们。毕竟,您可能需要收集度量数据,因为您需要创建一个实时仪表板,或者将度量数据提供给另一个应用程序。您可以通过报告(Reporter)向外部后端公开度量标准,而Flink为其中几个提供了实现(见表10-19)。
Reporter | Implementation |
---|---|
JMX | org.apache.flink.metrics.jmx.JMXReporter |
Graphite | org.apache.flink.metrics.graphite.GraphiteReporter |
Prometheus | org.apache.flink.metrics.prometheus.PrometheusReporter |
PrometheusPushGateway | org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporter |
StatsD | org.apache.flink.metrics.statsd.StatsDReporter |
Datadog | org.apache.flink.metrics.datadog.DatadogHttpReporter |
Slf4j | org.apache.flink.metrics.slf4j.Slf4jReporter |
如果您想使用未包含在上述列表中的度量后端,您还可以通过实现org.apache.flink.metrics.reporter.MetricReporter接口来定义您自己的报告程序。
Reporters需要在flink-conf.yaml中配置。向配置中添加以下代码行,将定义一个JMX报告器“my_reporter”,它监听端口9020-9040:
metrics.reporters: my_reporter
Metrics.reporter.my_jmx_reporter.class:org.apache.flink.metrics.jmx.JMXReporter
metrics.reporter.my_jmx_reporter.port: 9020-9040
有关每个受支持的Reporter程序的配置选项的完整列表,请参阅Flink文档。
监控延迟
延迟可能是您想要监控的第一个指标之一,以评估流作业的性能特征。同时,它也是在具有丰富语义(如Flink)的分布式流引擎中定义的最棘手的指标之一。在“Latency”中,我们将延迟大致定义为处理事件所需的时间。您可以想象,如果我们试图在具有复杂数据流的高速率流作业中跟踪每个事件的延迟,那么这个定义的精确实现在实践中可能会出现问题。考虑到窗口操作符使延迟跟踪更加复杂,如果一个事件贡献了几个窗口,我们需要报告第一次调用的延迟,还是需要等待,直到计算一个事件可能属于的所有窗口?如果一个窗口多次触发该怎么办?
Flink遵循一种简单的、低开销的方法来提供有用的延迟度量。它不是试图严格测量每个事件的延迟,而是通过定期在源上发出一条特殊记录,并允许用户跟踪该记录到达sink所需的时间来近似地度量延迟。这个特殊的记录称为延迟标记,它带有一个时间戳,指示它是何时发出的。
要启用延迟跟踪,您需要配置延迟标记从源发出的频率。你可以在ExecutionConfig中设置latencyTrackingInterval,如下所示:
env.getConfig.setLatencyTrackingInterval(500L)
间隔以毫秒为单位指定。当接收到延迟标记时,除了sink操作符外,所有的操作符都将它向下游发送。延迟标记使用与正常流记录相同的数据流通道和队列,因此其跟踪的延迟反映了等待处理的时间记录。但是,它们不度量处理记录所需的时间,或者记录在状态中等待直到处理它们的时间。
操作符将延迟统计信息保存在一个延迟指示器(gauge)中,其中包含最小值、最大值和平均值,以及50、95和99个百分点值。接收操作符对每个并行源实例接收的延迟标记进行统计,因此可以使用在接收处检查延迟标记来估算记录遍历数据流所需的时间。如果希望自定义操作符处的处理延迟标记,可以覆盖processLatencyMarker()方法,并使用LatencyMarker的方法getMarkedTime()、getVertexId()和getSubTaskIndex()检索相关信息。
注意时钟倾斜
如果您没有使用诸如NTP之类的自动时钟同步服务,集群机器的时钟可能会受到时钟歪斜的影响。在这种情况下,延迟跟踪估计是不可靠的,因为其当前实现默认时钟是同步的。
配置日志行为
日志记录是调试和理解应用程序行为的另一个重要工具。默认情况下,Flink使用SLF4J日志抽象和log4j日志框架。示例10-9显示了一个MapFunction,它记录每个输入记录的转换。
import org.apache.flink.api.common.functions.MapFunction
import org.slf4j.LoggerFactory
import org.slf4j.Logger
class MyMapFunction extends MapFunction[Int, String] {
Logger LOG = LoggerFactory.getLogger(MyMapFunction.class)
override def map(value: Int): String = {
LOG.info("Converting value {} to string.", value)
value.toString
}
}
要更改log4j记录器的属性,请修改conf/文件夹中的log4j.properties文件。例如,下面的行将根日志级别设置为“warning”:
log4j.rootLogger=WARN
要设置此文件的自定义文件名和位置,请传递-Dlog4j.configuration=参数到JVM。Flink还提供了log4j-cli.properties文件,用于命令行客户机和log4j-yarn-session.properties文件,用于YARN session环境中的命令行客户端。
log4j的另一种替代方法是logback,而且Flink还为此后端提供了默认的配置文件。要使用logback而不是log4j,您需要从lib/文件夹中删除log4j。有关如何设置和配置后端,请参阅Flink的文档和logback手册。
结束语
在本章中,我们讨论了如何在生产环境中运行、管理和监视Flink应用程序。我们解释了收集和访问系统和应用程序指标的Flink组件,如何配置日志系统,以及如何使用命令行客户机和REST API启动、停止、恢复和重新部署应用程序。