实时报表与table api
Apache Flink提供了一个表API作为一个统一的关系型API用于批处理和流处理,也就是说,查询在无边界的实时流或有边界的批处理数据集上以相同的语义执行,并产生相同的结果。Flink中的表API通常用于简化数据分析、数据管道和ETL应用程序的定义。
你要建什么?
在本教程中,您将学习如何构建一个实时仪表板,以按帐户跟踪金融交易。该管道将从Kafka读取数据,并通过Grafana将结果写入MySQL可视化。
前提
本演练假设您熟悉Java或Scala,但即使您来自不同的编程语言,也应该能够理解。本文还假设您熟悉基本的关系概念,如SELECT和GROUP BY子句。
开发环境
- Java 11
- Maven
- Docker
所需的配置文件可以在flink-playgrounds存储库中找到。下载后,在IDE中打开项目flink-playground/table-walkthrough,并导航到文件SpendReport。
EnvironmentSettings settings = EnvironmentSettings.inStreamingMode();
TableEnvironment tEnv = TableEnvironment.create(settings);
tEnv.executeSql("CREATE TABLE transactions (\n" +
" account_id BIGINT,\n" +
" amount BIGINT,\n" +
" transaction_time TIMESTAMP(3),\n" +
" WATERMARK FOR transaction_time AS transaction_time - INTERVAL '5' SECOND\n" +
") WITH (\n" +
" 'connector' = 'kafka',\n" +
" 'topic' = 'transactions',\n" +
" 'properties.bootstrap.servers' = 'kafka:9092',\n" +
" 'format' = 'csv'\n" +
")");
tEnv.executeSql("CREATE TABLE spend_report (\n" +
" account_id BIGINT,\n" +
" log_ts TIMESTAMP(3),\n" +
" amount BIGINT\n," +
" PRIMARY KEY (account_id, log_ts) NOT ENFORCED" +
") WITH (\n" +
" 'connector' = 'jdbc',\n" +
" 'url' = 'jdbc:mysql://mysql:3306/sql-demo',\n" +
" 'table-name' = 'spend_report',\n" +
" 'driver' = 'com.mysql.jdbc.Driver',\n" +
" 'username' = 'sql-demo',\n" +
" 'password' = 'demo-sql'\n" +
")");
Table transactions = tEnv.from("transactions");
report(transactions).executeInsert("spend_report");
测试
该项目包含第二个测试类SpendReportTest,用于验证报告的逻辑。它以批处理模式创建表环境。
EnvironmentSettings settings = EnvironmentSettings.inBatchMode();
TableEnvironment tEnv = TableEnvironment.create(settings);
Flink的一个独特属性是它在批处理和流处理之间提供一致的语义。这意味着您可以在静态数据集上以批处理模式开发和测试应用程序,并作为流应用程序部署到生产中。
一次尝试
现在有了Job设置的框架,就可以添加一些业务逻辑了。目标是构建一个报告,显示每个帐户在一天中的每个小时的总花费。这意味着时间戳列的粒度需要从毫秒向下舍入到小时。
Flink支持使用纯SQL或Table API开发关系应用程序。Table API是受SQL启发的一种流畅的DSL,可以用Python、Java或Scala编写,并支持强大的IDE集成。就像SQL查询一样,Table程序可以选择所需的字段并根据您的键进行分组。这些特性,以及像floor和sum这样的内置功能,使您能够编写这个报告。
public static Table report(Table transactions) {
return transactions.select(
$("account_id"),
$("transaction_time").floor(TimeIntervalUnit.HOUR).as("log_ts"),
$("amount"))
.groupBy($("account_id"), $("log_ts"))
.select(
$("account_id"),
$("log_ts"),
$("amount").sum().as("amount"));
}
UDF
Flink包含有限数量的内置函数,有时需要使用用户定义的函数对其进行扩展。如果地板不是预定义的,您可以自己实现它。
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import org.apache.flink.table.annotation.DataTypeHint;
import org.apache.flink.table.functions.ScalarFunction;
public class MyFloor extends ScalarFunction {
public @DataTypeHint("TIMESTAMP(3)") LocalDateTime eval(
@DataTypeHint("TIMESTAMP(3)") LocalDateTime timestamp) {
return timestamp.truncatedTo(ChronoUnit.HOURS);
}
}
And then quickly integrate it in your application.
public static Table report(Table transactions) {
return transactions.select(
$("account_id"),
call(MyFloor.class, $("transaction_time")).as("log_ts"),
$("amount"))
.groupBy($("account_id"), $("log_ts"))
.select(
$("account_id"),
$("log_ts"),
$("amount").sum().as("amount"));
}
该查询使用事务表中的所有记录,计算报告,并以一种高效、可伸缩的方式输出结果。使用该实现运行测试将通过。
Adding Windows
基于时间对数据进行分组是数据处理中的一种典型操作,特别是在处理无限流时。基于时间的分组称为窗口,Flink提供了灵活的窗口语义。最基本的窗口类型被称为滚筒式窗口,它有固定的大小,桶不重叠。
public static Table report(Table transactions) {
return transactions
.window(Tumble.over(lit(1).hour()).on($("transaction_time")).as("log_ts"))
.groupBy($("account_id"), $("log_ts"))
.select(
$("account_id"),
$("log_ts").start().as("log_ts"),
$("amount").sum().as("amount"));
}
这将您的应用程序定义为使用基于时间戳列的一小时滚动窗口。因此,时间戳为2019-06-01 01:23:47的一行就会出现在2019-06-01 01:00:00窗口中。
基于时间的聚合是唯一的,因为与其他属性相比,时间在连续流应用程序中通常是向前移动的。与floor和UDF不同,窗口函数是intrinsic,它允许运行时应用额外的优化。在批处理上下文中,窗口为按时间戳属性分组记录提供了方便的API。
使用该实现运行测试也会通过。
再一次,用流
就是这样,一个功能完整、有状态的分布式流媒体应用程序!该查询持续消耗来自Kafka的事务流,计算每小时的开销,并在结果准备好后立即发出结果。由于输入是无界的,所以查询会一直运行,直到手动停止为止。因为Job使用基于时间窗口的聚合,所以Flink可以执行特定的优化,比如当框架知道某个特定窗口将没有更多记录到达时,可以执行状态清理。
table playground 被完全docker化(容器化),可以作为流应用程序在本地运行。该环境包含一个Kafka主题,一个连续数据生成器,MySql和Grafana。
从table-walkthrough文件夹中启动docker-compose脚本。
$ docker-compose build
$ docker-compose up -d
You can see information on the running job via the Flink console.
$ docker-compose exec mysql mysql -Dsql-demo -usql-demo -pdemo-sql
mysql> use sql-demo;
Database changed
mysql> select count(*) from spend_report;
+----------+
| count(*) |
+----------+
| 110 |
+----------+
Finally, go to Grafana to see the fully visualized result!
Flink operator playground
在各种环境中部署和操作Apache Flink有许多方法。尽管存在这种变化,Flink Cluster的基本构建块仍然是相同的,并且应用了类似的操作原则。
在这个一揽子里,你将学习如何管理和运行Flink作业。您将了解如何部署和监视应用程序,体验Flink如何从Job失败中恢复,并执行日常操作任务,如升级和缩放。
playground刨析
这个playground由一个长时间的Flink Session Cluster和一个Kafka Cluster组成。
一个Flink Cluster总是由一个JobManager和一个或多个Flink taskmanager组成。JobManager负责处理Job提交,对Job进行监督以及资源管理。Flink TaskManagers是工作进程,负责执行构成Flink Job的实际任务。在这个操场上,你将从一个任务管理器开始,但随后扩展到更多的任务管理器。此外,这个playground附带一个专用的客户端容器,我们最初使用它提交Flink Job,然后执行各种操作任务。
Kafka集群由一个Zookeeper服务器和一个Kafka Broker组成。
当操场启动时,一个名为Flink Event Count的Flink作业将被提交给JobManager。另外,创建了两个Kafka Topics输入和输出。
Job使用来自输入主题的ClickEvents,每个都带有时间戳和页面。然后按页键入事件并在15秒内计数窗口。结果被写入输出主题。
有6个不同的页面,我们在15秒内为每个页面生成1000个点击事件。因此,Flink作业的输出应该显示每个页面和窗口有1000个视图。
Starting the Playground
操场环境只需要几个步骤就可以设置好。我们将指导您完成必要的命令,并展示如何验证一切都正常运行。
我们假设你的机器上安装了Docker(1.12+)和Docker -compose(2.1+)。
所需的配置文件可以在flink-playgrounds存储库中找到。首先签出代码并构建docker映像:
git clone https://github.com/apache/flink-playgrounds.git
cd flink-playgrounds/operations-playground
docker-compose build
然后在启动playground目录之前,在Docker主机上创建checkpoint和savepoint目录(这些卷由jobmanager和taskmanager挂载,在Docker -compose.yaml中指定):
mkdir -p /tmp/flink-checkpoints-directory
mkdir -p /tmp/flink-savepoints-directory
然后开始playground
docker-compose up -d
然后,你可以使用下面的命令检查正在运行的Docker容器:
docker-compose ps
Name Command State Ports
-----------------------------------------------------------------------------------------------------------------------------
operations-playground_clickevent-generator_1 /docker-entrypoint.sh java ... Up 6123/tcp, 8081/tcp
operations-playground_client_1 /docker-entrypoint.sh flin ... Exit 0
operations-playground_jobmanager_1 /docker-entrypoint.sh jobm ... Up 6123/tcp, 0.0.0.0:8081->8081/tcp
operations-playground_kafka_1 start-kafka.sh Up 0.0.0.0:9094->9094/tcp
operations-playground_taskmanager_1 /docker-entrypoint.sh task ... Up 6123/tcp, 8081/tcp
operations-playground_zookeeper_1 /bin/sh -c /usr/sbin/sshd ... Up 2181/tcp, 22/tcp, 2888/tcp, 3888/tcp
这表明客户端容器已经成功提交了Flink作业(Exit 0),并且所有集群组件和数据生成器都在运行(Up)。
你可以通过调用下面命令停止:
docker-compose down -v
Entering the Playground
在这个Playground上有很多东西你可以尝试和检查。在下面的两个小节中,我们将向您展示如何与Flink集群交互,并演示Flink的一些关键特性。
Flink WebUI
观察Flink集群最自然的起点是在http://localhost:8081下公开的web。如果一切顺利,您将看到集群最初由一个TaskManager组成,并执行一个名为Click Event Count的作业。
Flink的web包含了很多关于Flink集群及其作业(JobGraph, Metrics, checkpoint point Statistics, TaskManager Status,…)的有用和有趣的信息。
Logs
JobManager
JobManager日志可以通过docker-compose进行跟踪。
docker-compose logs -f jobmanager
启动之后,可以看到每次完成的checkpoint日志记录
TaskManager
TaskManager日志可以通过docker-compose进行跟踪。
docker-compose logs -f taskmanager
初始启动后,您应该主要看到每个检查点完成的日志消息。
Flink CLI
Flink CLI可以在客户端容器内使用。例如,打印Flink CLI的帮助信息。
docker-compose run --no-deps client flink --help
Flink REST API
Flink REST API通过主机上的localhost:8081或客户端容器上的作业管理器:8081公开,例如,要列出所有当前运行的作业,你可以运行:
curl localhost:8081/jobs
Kafka Topics
你可以通过运行查看写入Kafka Topics的记录。
//input topic (1000 records/s)
docker-compose exec kafka kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic input
//output topic (24 records/min)
docker-compose exec kafka kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic output
Time to Play!
现在你已经学习了如何与Flink和Docker容器交互,让我们来看看一些常见的操作任务,你可以在我们的操场上尝试。所有这些任务都是相互独立的,也就是说,你可以以任何顺序执行它们。大多数任务可以通过CLI和REST API执行。
Listing Running Jobs
Command
docker-compose run --no-deps client flink list
Expected Output
Waiting for response...
------------------ Running/Restarting Jobs -------------------
16.07.2019 16:37:55 : <job-id> : Click Event Count (RUNNING)
--------------------------------------------------------------
No scheduled jobs.
Rest api
Request
curl localhost:8081/jobs
Expected Response (pretty-printed)
{
"jobs": [
{
"id": "<job-id>",
"status": "RUNNING"
}
]
}
JobID在提交时被分配给Job,它需要通过CLI或REST API对Job执行操作。
观察故障和恢复
Flink在(部分)失败时提供了恰好一次的处理保证。在这个playground上,你可以观察并在某种程度上验证这种行为。
step 1: 观察Output
如上所述,在这个playground上生成的事件使每个窗口恰好包含一千条记录。因此,为了验证Flink成功地从TaskManager故障中恢复,没有数据丢失或重复,您可以跟踪输出主题,并检查—恢复后—所有窗口都存在且计数正确。
为此,从输出主题开始读取,并保持该命令运行,直到恢复后(步骤3)。
docker-compose exec kafka kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic output
step 2: 引入故障
为了模拟部分故障,您可以杀死TaskManager。在生产设置中,这可能对应于TaskManager进程、TaskManager机器的丢失,或者只是一个从框架或用户代码中抛出的短暂异常(例如,由于外部资源临时不可用)。
docker-compose kill taskmanager
几秒钟后,JobManager会注意到TaskManager的丢失,取消受影响的Job,并立即重新提交它进行恢复。当Job重新启动时,它的任务保持在SCHEDULED状态,这由紫色方块表示(参见下面的截图)。
注意:即使作业的任务处于SCHEDULED状态而不是RUNNING,作业的整体状态仍显示为RUNNING。
此时,Job的任务不能从SCHEDULED状态移动到RUNNING状态,因为没有资源(TaskManagers提供的TaskSlots)可用于运行任务。在新的TaskManager可用之前,Job将经历一个取消和重新提交的循环。
与此同时,数据生成器继续将ClickEvents推入输入主题。这类似于实际的生产设置,即在Job关闭时生成数据。
Step 3: Recovery
重启TaskManager后,它会重新连接到JobManager。
docker-compose up -d taskmanager
当JobManager被通知有新的TaskManager时,它将正在恢复的Job调度到新可用的taskslot中。在重新启动时,任务将从失败前的最后一个成功检查点恢复其状态,并切换到RUNNING状态。
Job将快速处理来自Kafka的全部输入事件积压(宕机期间积累的),并以更高的速率(> 24条记录/分钟)产生输出,直到它到达流的头部。在输出中,您将看到所有时间窗口的所有键(页面)都存在,并且每个计数正好是1000。因为我们在“至少一次”模式下使用FlinkKafkaProducer,所以有可能你会看到一些重复的输出记录。
注意:大多数生产设置依赖于资源管理器(Kubernetes, Yarn)来自动重启失败的进程。
升级和调整job
升级Flink Job通常包括两个步骤:首先,通过一个保存点优雅地停止Flink Job。保存点是定义良好的全局一致时间点(类似于检查点)上的完整应用程序状态的一致快照。第二,升级后的Flink Job从保存点启动。在这种情况下,“升级”可以是不同的意思,包括以下内容:
- 对配置的升级(包括Job的并行性)
- 作业拓扑的升级(添加/删除操作符)
- 对Job的用户定义功能的升级
在开始升级之前,您可能希望开始跟踪输出主题,以便观察在升级过程中没有数据丢失或损坏。
docker-compose exec kafka kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic output
Step 1: Stopping the Job
要优雅地停止Job,您需要使用CLI或REST API的“stop”命令。为此,您需要作业的JobID,您可以通过列出所有正在运行的作业或从web中获取。使用JobID,你可以继续停止作业:
客户端方式:CLI
执行命令:
docker-compose run --no-deps client flink stop <job-id>
预期会输出:
Suspending job "<job-id>" with a savepoint.
Savepoint completed. Path: file:<savepoint-path>
Rest API方式:
Request
# triggering stop
curl -X POST localhost:8081/jobs/<job-id>/stop -d '{"drain": false}'
Expected Response (pretty-printed)
{
"request-id": "<trigger-id>"
}
Request
# check status of stop action and retrieve savepoint path
curl localhost:8081/jobs/<job-id>/savepoints/<trigger-id>
Expected Response (pretty-printed)
{
"status": {
"id": "COMPLETED"
},
"operation": {
"location": "<savepoint-path>"
}
}
step 2a: 不带更改重启Job
现在可以从这个保存点重新启动升级后的作业。为了简单起见,您可以在不做任何更改的情况下重新启动它。
CLI:
执行命令:
docker-compose run --no-deps client flink run -s <savepoint-path> \
-d /opt/ClickCountJob.jar \
--bootstrap.servers kafka:9092 --checkpointing --event-time
预期输出:
Job has been submitted with JobID <job-id>
Rest Api:
Request
# Uploading the JAR from the Client container
docker-compose run --no-deps client curl -X POST -H "Expect:" \
-F "jarfile=@/opt/ClickCountJob.jar" http://jobmanager:8081/jars/upload
Expected Response (pretty-printed)
{
"filename": "/tmp/flink-web-<uuid>/flink-web-upload/<jar-id>",
"status": "success"
}
Request
# Submitting the Job
curl -X POST http://localhost:8081/jars/<jar-id>/run \
-d '{"programArgs": "--bootstrap.servers kafka:9092 --checkpointing --event-time", "savepointPath": "<savepoint-path>"}'
Expected Response (pretty-printed)
{
"jobid": "<job-id>"
}
当Job再次运行时,您将在输出Topic中看到,当Job处理停机期间积累的backlog时,记录的生成速度更快。此外,您将看到在升级期间没有数据丢失:所有窗口的计数都正好为1000。
Step 2b: Restart Job with a Different Parallelism (Rescaling)
或者,您也可以通过在重新提交期间传递不同的并行性,从这个保存点重新调整Job。
方式一:
Command:
docker-compose run --no-deps client flink run -p 3 -s <savepoint-path> \
-d /opt/ClickCountJob.jar \
--bootstrap.servers kafka:9092 --checkpointing --event-time
Expected Output
Starting execution of program
Job has been submitted with JobID <job-id>
方式二:
Request
# Uploading the JAR from the Client container
docker-compose run --no-deps client curl -X POST -H "Expect:" \
-F "jarfile=@/opt/ClickCountJob.jar" http://jobmanager:8081/jars/upload
Expected Response (pretty-printed)
{
"filename": "/tmp/flink-web-<uuid>/flink-web-upload/<jar-id>",
"status": "success"
}
Request
# Submitting the Job
curl -X POST http://localhost:8081/jars/<jar-id>/run \
-d '{"parallelism": 3, "programArgs": "--bootstrap.servers kafka:9092 --checkpointing --event-time", "savepointPath": "<savepoint-path>"}'
Expected Response (pretty-printed
{
"jobid": "<job-id>"
}
现在,Job已经被重新提交,但是随着并行度的增加(2个可用,3个需要),没有足够的taskslot来执行它,所以它不会启动。
docker-compose scale taskmanager=2
你可以添加第二个TaskManager和两个taskslot到Flink集群,它会自动注册到JobManager。在添加TaskManager后不久,任务应该再次开始运行。
一旦作业再次“running”,您将在输出Topic中看到,在缩放过程中没有数据丢失:所有窗口的计数都正好为1000。
Querying the Metrics of a Job(查询作业的各种指标)
JobManager通过其REST API公开系统和用户指标。
端点取决于这些度量的范围。作用域为Job的指标可以通过jobs/< Job id>/ Metrics列出。可以通过get查询参数查询指标的实际值。
Request
curl "localhost:8081/jobs/<jod-id>/metrics?get=lastCheckpointSize"
Expected Response (pretty-printed; no placeholders)
[
{
"id": "lastCheckpointSize",
"value": "9378"
}
]
REST API不仅可以用于查询指标,还可以检索关于正在运行的作业状态的详细信息。
Request
# find the vertex-id of the vertex of interest
curl localhost:8081/jobs/<jod-id>
Expected Response (pretty-printed)
{
"jid": "<job-id>",
"name": "Click Event Count",
"isStoppable": false,
"state": "RUNNING",
"start-time": 1564467066026,
"end-time": -1,
"duration": 374793,
"now": 1564467440819,
"timestamps": {
"CREATED": 1564467066026,
"FINISHED": 0,
"SUSPENDED": 0,
"FAILING": 0,
"CANCELLING": 0,
"CANCELED": 0,
"RECONCILING": 0,
"RUNNING": 1564467066126,
"FAILED": 0,
"RESTARTING": 0
},
"vertices": [
{
"id": "<vertex-id>",
"name": "ClickEvent Source",
"parallelism": 2,
"status": "RUNNING",
"start-time": 1564467066423,
"end-time": -1,
"duration": 374396,
"tasks": {
"CREATED": 0,
"FINISHED": 0,
"DEPLOYING": 0,
"RUNNING": 2,
"CANCELING": 0,
"FAILED": 0,
"CANCELED": 0,
"RECONCILING": 0,
"SCHEDULED": 0
},
"metrics": {
"read-bytes": 0,
"read-bytes-complete": true,
"write-bytes": 5033461,
"write-bytes-complete": true,
"read-records": 0,
"read-records-complete": true,
"write-records": 166351,
"write-records-complete": true
}
},
{
"id": "<vertex-id>",
"name": "ClickEvent Counter",
"parallelism": 2,
"status": "RUNNING",
"start-time": 1564467066469,
"end-time": -1,
"duration": 374350,
"tasks": {
"CREATED": 0,
"FINISHED": 0,
"DEPLOYING": 0,
"RUNNING": 2,
"CANCELING": 0,
"FAILED": 0,
"CANCELED": 0,
"RECONCILING": 0,
"SCHEDULED": 0
},
"metrics": {
"read-bytes": 5085332,
"read-bytes-complete": true,
"write-bytes": 316,
"write-bytes-complete": true,
"read-records": 166305,
"read-records-complete": true,
"write-records": 6,
"write-records-complete": true
}
},
{
"id": "<vertex-id>",
"name": "ClickEventStatistics Sink",
"parallelism": 2,
"status": "RUNNING",
"start-time": 1564467066476,
"end-time": -1,
"duration": 374343,
"tasks": {
"CREATED": 0,
"FINISHED": 0,
"DEPLOYING": 0,
"RUNNING": 2,
"CANCELING": 0,
"FAILED": 0,
"CANCELED": 0,
"RECONCILING": 0,
"SCHEDULED": 0
},
"metrics": {
"read-bytes": 20668,
"read-bytes-complete": true,
"write-bytes": 0,
"write-bytes-complete": true,
"read-records": 6,
"read-records-complete": true,
"write-records": 0,
"write-records-complete": true
}
}
],
"status-counts": {
"CREATED": 0,
"FINISHED": 0,
"DEPLOYING": 0,
"RUNNING": 4,
"CANCELING": 0,
"FAILED": 0,
"CANCELED": 0,
"RECONCILING": 0,
"SCHEDULED": 0
},
"plan": {
"jid": "<job-id>",
"name": "Click Event Count",
"type": "STREAMING",
"nodes": [
{
"id": "<vertex-id>",
"parallelism": 2,
"operator": "",
"operator_strategy": "",
"description": "ClickEventStatistics Sink",
"inputs": [
{
"num": 0,
"id": "<vertex-id>",
"ship_strategy": "FORWARD",
"exchange": "pipelined_bounded"
}
],
"optimizer_properties": {}
},
{
"id": "<vertex-id>",
"parallelism": 2,
"operator": "",
"operator_strategy": "",
"description": "ClickEvent Counter",
"inputs": [
{
"num": 0,
"id": "<vertex-id>",
"ship_strategy": "HASH",
"exchange": "pipelined_bounded"
}
],
"optimizer_properties": {}
},
{
"id": "<vertex-id>",
"parallelism": 2,
"operator": "",
"operator_strategy": "",
"description": "ClickEvent Source",
"optimizer_properties": {}
}
]
}
}
请参考REST API参考,以获得可能查询的完整列表,包括如何查询不同范围的指标(例如TaskManager指标);
变型
您可能已经注意到,Click Event Count应用程序总是以–checkpointing and --event-time 项目参数开始。通过命令添加上面参数在docker-compose.yaml的客户端容器的命令中忽略这些。您可以更改Job的行为。
- –checkpoint启用检查点,这是Flink的容错机制。如果您在没有它的情况下运行并经历失败和恢复,您应该会看到数据实际上丢失了。
- –event-time为Job启用事件时间语义。当禁用时,Job将基于时钟时间而不是ClickEvent的时间戳为窗口分配事件。因此,每个窗口的事件数不再是1000个。
Click Event Count应用程序还有另一个选项(默认关闭),您可以启用该选项来查看该作业在反压力下的行为。您可以在docker-compose.yaml的客户端容器的命令中添加此选项。
- –backpressure 增加一个额外的操作到作业的中间,在偶数分钟(例如,在10:12,但不在10:13)造成严重的反压。这可以通过检查各种网络指标来观察,如outputQueueLength和outPoolUsage,或使用web中可用的反压力监控。