运行 flink 程序时会经常需要查看当前程序的运行状况,flink 提供了 UI 界面,有比较详细的统计信息。但是 UI 界面也有不完善的地方,比如想要获取 flink 的实时吞吐。本文通过示例介绍通过 flink REST API 获取 flink 实时吞吐。
为了更简单的表示,以下的 REST API 请求都省略了 JobManager 的地址和端口(对于 flink on yarn 模式来说,则是省略了 RM 代理的 JobManager UI 地址,例如 http://yarn-resource-manager-ui/proxy/application_155316436xxxx_xxxx)。
GET /v1/jobs
该接口返回集群中的 job 列表及运行状态。返回结果示例:
{
jobs: [{
id: "ce793f18efab10127f0626a37ff4b4d4",
status: "RUNNING"
}
]
}
对 run a single job with flink on yarn 模式来说,一般只有一个 RUNNING job,其 id 即为 “ce793f18efab10127f0626a37ff4b4d4”.
# GET /v1/jobs/
GET /v1/jobs/ce793f18efab10127f0626a37ff4b4d4
该接口返回 job 的详细信息。返回结果示例:
{
jid: "ce793f18efab10127f0626a37ff4b4d4",
name: "Test",
isStoppable: false,
state: "RUNNING",
start - time: 1551577191874,
end - time: -1,
duration: 295120489,
now: 1551872312363,
timestamps: {
CANCELLING: 0,
FAILED: 0,
RESTARTING: 1551577191874,
SUSPENDED: 0,
RUNNING: 1551577191896,
CANCELED: 0,
CREATED: 1551577191874,
RECONCILING: 0,
FINISHED: 0,
SUSPENDING: 0,
FAILING: 0
},
vertices: [{
id: "cbc357ccb763df2852fee8c4fc7d55f2",
name: "Source: Custom Source -> Flat Map",
parallelism: 12,
status: "RUNNING",
start - time: 1551577260649,
end - time: -1,
duration: 295051714,
tasks: {
RUNNING: 12,
RECONCILING: 0,
SCHEDULED: 0,
DEPLOYING: 0,
CANCELED: 0,
FAILED: 0,
CREATED: 0,
FINISHED: 0,
CANCELING: 0
},
metrics: {
read - bytes: 0,
read - bytes - complete: true,
write - bytes: 92306188,
write - bytes - complete: true,
read - records: 0,
read - records - complete: true,
write - records: 244844,
write - records - complete: true
}
}, {
id: "9dd63673dd41ea021b896d5203f3ba7c",
name: "Sink: Unnamed",
parallelism: 12,
status: "RUNNING",
start - time: 1551577260652,
end - time: -1,
duration: 295051711,
tasks: {
RUNNING: 12,
RECONCILING: 0,
SCHEDULED: 0,
DEPLOYING: 0,
CANCELED: 0,
FAILED: 0,
CREATED: 0,
FINISHED: 0,
CANCELING: 0
},
metrics: {
read - bytes: 93938476,
read - bytes - complete: true,
write - bytes: 0,
write - bytes - complete: true,
read - records: 244844,
read - records - complete: true,
write - records: 0,
write - records - complete: true
}
}
],
status - counts: {
RUNNING: 2,
RECONCILING: 0,
SCHEDULED: 0,
DEPLOYING: 0,
CANCELED: 0,
FAILED: 0,
CREATED: 0,
FINISHED: 0,
CANCELING: 0
},
plan: {
jid: "ce793f18efab10127f0626a37ff4b4d4",
name: "Test",
nodes: [{
id: "9dd63673dd41ea021b896d5203f3ba7c",
parallelism: 12,
operator: "",
operator_strategy: "",
description: "Sink: Unnamed",
inputs: [{
num: 0,
id: "cbc357ccb763df2852fee8c4fc7d55f2",
ship_strategy: "FORWARD",
exchange: "pipelined_bounded"
}
],
optimizer_properties: {}
}, {
id: "cbc357ccb763df2852fee8c4fc7d55f2",
parallelism: 12,
operator: "",
operator_strategy: "",
description: "Source: Custom Source -> Flat Map",
optimizer_properties: {}
}
]
}
}
该接口详细列出了该 job 的信息。包括作业 id,名称,source 是否可以接收停止信号,job 运行状态,job 开始时间/结束时间/已经运行时长以及当前时间等。我们这里需要的是 job 的运行顶点信息 (vertices) 或执行计划 (plan) 中的顶点/节点信息。vertices 和 plan 的区别在于:plan 是执行计划,在 JobGraph 生成时就已经生成;vertices 是运行时的顶点,包含运行时节点的 metric 信息,这里的 metric 信息比较局限,一般只包含输入/输出元素总数和输入/输出字节总数。
需要额外说明的是,这里的输入/输出只是在 flink 的各个节点之间,不包含与外界组件的交互信息。所以,这里的统计里, flink source 的 read-bytes/read-records 都是 0;flink sink 的 write-bytes/write-records 都是 0。在 flink UI 上的显示也是如此。
那么,该怎样获取 flink 的实时吞吐呢?从这个接口我们获取到了各顶点 (vertices) 对应的 id ,分别是 Source: Custom Source -> Flat Map: cbc357ccb763df2852fee8c4fc7d55f2 (flink 将原来的两个 operator 通过 channing优化合并成了一个,这里不再展开),Sink: Unnamed: 9dd63673dd41ea021b896d5203f3ba7c。我们可以通过获取 source 的输出或 sink 的输入来统计 flink 的整体吞吐。
另外,
# GET /v1/jobs//vertices/
GET /v1/jobs/ce793f18efab10127f0626a37ff4b4d4/vertices/cbc357ccb763df2852fee8c4fc7d55f2
可以获取 cbc357ccb763df2852fee8c4fc7d55f2 这个 vertex 的各个 subtask 的运行概况,这里不再赘述。
# GET /v1/jobs//vertices//subtasks/metrics
GET /v1/jobs/ce793f18efab10127f0626a37ff4b4d4/vertices/cbc357ccb763df2852fee8c4fc7d55f2/subtasks/metrics
这个接口会列出所有在这个 vertex 上注册的 metric。
有下游节点的 vertex 一般都有 numRecordsOutPerSecond
和 numBytesOutPerSecond
的 metric,分别代表这个 vertex 每秒发送给下游 vertex 的元素个数和字节数。则可以通过以下方式获取
# GET /v1/jobs//vertices//subtasks/metrics?get=numRecordsOutPerSecond,numBytesOutPerSecond
GET /v1/jobs/ce793f18efab10127f0626a37ff4b4d4/vertices/cbc357ccb763df2852fee8c4fc7d55f2/subtasks/metrics?get=numRecordsOutPerSecond,numBytesOutPerSecond
返回结果示例:
[{
id: "numRecordsOutPerSecond",
min: 1.65,
max: 1.7,
avg: 1.6666666666666663,
sum: 19.999999999999996
}, {
id: "numBytesOutPerSecond",
min: 282.75,
max: 295.31666666666666,
avg: 287.9861111111111,
sum: 3455.833333333333
}
]
min, max, avg, sum 分别代表该 vertex 的所有 subtasks 中,该 metric 的最小值、最大值、平均值和总和,很明显,这里的 sum 值就是 该 vertex 的吞吐!
如果不关心某些聚合结果,可以通过 agg
参数过滤。例如,只想统计 min 和 sum 值:
# GET /v1/jobs//vertices//subtasks/metrics?get=numRecordsOutPerSecond,numBytesOutPerSecond&agg=min,max,sum
GET /v1/jobs/ce793f18efab10127f0626a37ff4b4d4/vertices/cbc357ccb763df2852fee8c4fc7d55f2/subtasks/metrics?get=numRecordsOutPerSecond,numBytesOutPerSecond&agg=min,sum
则只会列出:
[{
id: "numRecordsOutPerSecond",
min: 1.65,
sum: 19.999999999999996
}, {
id: "numBytesOutPerSecond",
min: 282.75,
sum: 3455.833333333333
}
]
由于 flink 消费/写入 kafka 的场景很多,用户常常需要获取当前 kafka 的消费情况,当然用户可以通过其他工具获取 kafka topic 的消费进度。这里提供一种上文提到的 REST API 获取 flink 内置的 kafka metric 信息。
仍然通过第 3 节提到的 REST API 接口:
GET /v1/jobs//vertices//subtasks/metrics
之所以能够使用这种方式,是因为 flink 将kafka 自身提供的 metrics 信息 导入到了 flink metric 系统,包含:
通过 GET /v1/jobs/
列出所有 metric 后,可以搜索 KafkaConsumer
相关的 metric。这里的 metric 都在原来 kafka metric 的基础上加上了 flink 算子名作为前缀。
如果我们没有指定 kafka consumer 算子名,默认为 Source__Custom_Source.KafkaConsumer,例如 records-consumed-rate
这个 kafka metric 在 flink metric 系统中叫做 Source__Custom_Source.KafkaConsumer.records-consumed-rate
。
当然我们还关心每个 partition 的消费情况,在官方版本 flink 中,没有一个 metric 可以计算 lag 数,但是 flink 提供了每个 partition 的 currentOffsets 和 committedOffsets:
Source__Custom_Source.KafkaConsumer.topic..partition..currentOffsets
Source__Custom_Source.KafkaConsumer.topic..partition..committedOffsets
topic_name
和 partition_num
替换为需要监控的 topic 名称和 partition ,例如我们要监控名为 words
的 partition 0
:
GET /v1/jobs//vertices//subtasks/metrics?get=Source__Custom_Source.KafkaConsumer.topic.words.partition.0.currentOffsets,Source__Custom_Source.KafkaConsumer.topic.words.partition.0.committedOffsets