Flink 如何定位背压来源

Flink 版本:1.13.0

在过去的几年里,背压的问题从不同的角度得到了解决。然而,在判断和分析背压来源时,最近的 Flink 版本发生了很大的变化(尤其是在 Flink 1.13 中新增了监控指标和 Web UI)。这篇文章将一起看一下其中的一些变化,并详细介绍如何追踪背压的来源,但首先……

1. 什么是背压?

Flink 如何处理背压 这篇文章虽然比较旧,但比较准确的解释了背压。如果您不了解背压这个概念,我强烈建议您阅读一下。如果想更深入、更底层地了解该话题以及 Flink 的网络堆栈是如何工作的,可以查阅咱们从头到尾讲一次 Flink 网络流控和反压剖析

从高层次上理解,如果作业图中的某些算子无法以接收记录相同的速度处理记录,就会发生背压。运行这个慢算子的子任务的输入缓冲区就会被填满。一旦输入缓冲区被填满,背压就会传播到上游子任务的输出缓冲区。一旦上游子任务的输出缓冲区被填满,上游子任务就被迫降低处理速度,来匹配慢算子的处理速度,最终导致下游出现瓶颈。背压一步一步向上传播,最终到达 Source 算子。

只要负载和可用资源不发生变化,并且没有算子产生短暂的数据突增(如窗口算子),这些输入/输出缓冲区应该只会处于两种状态:要么几乎为空,要么几乎被填满。如果下游算子或者子任务能够跟的上上游数据的输入,那么缓冲区是空的。如果不是,则缓冲区会被填满。在极少数情况下,网络交换实际上也是我们作业的瓶颈,下游任务有空的输入缓冲区,而上游输出缓冲区已满。事实上,检查缓冲区的利用率指标是几年前 Nico Kruber 推荐的关于如何检测和分析背压的基础。正如我在开头提到的,Flink 现在提供了更好的工具来完成同样的工作,但在我们开始之前,先提出两个问题。

1.1 为什么需要关心背压?

背压是我们机器或者算子过载的重要指标。背压的积累会直接影响到系统端到端的延迟,因为记录在被处理之前在队列中需要等待更长的时间。其次,在背压下对齐 Checkpoint 也需要更长的时间,而非对齐的 Checkpoint 也会变得越来越大。如果您正在为 Checkpoint Barriers 传播时间而苦恼,那么了解背压很可能有助于解决问题。最后,我们可能只想优化作业来降低运行作业的成本。

为了解决所有问题,需要了解背压,然后定位并分析它。

1.2 为什么不需要关心背压?

坦率地说,我们不必总是关心背压的存在。根据定义,如果没有背压意味着我们的集群至少有一点点未充分利用或者过度配置。如果我们想最大程度的减少用空闲资源,我们无法避免产生一些背压。对于批处理来说更如此。

2. 如何检测和追踪背压的来源?

检测背压的一种方法是使用监控指标,但在 Flink 1.13 中我们不需再深入去了解。在大多数情况下,只需查看 Web UI 中的作业图就足够了。

在上面的例子中首先要注意的是,不同的任务有不同的颜色。这些颜色代表了两个因素的组合:任务的背压程度以及忙碌程度。空闲任务用蓝色表示,全负荷忙碌任务用红色表示,而全负荷背压任务用黑色表示。介于两者之间的都会用这三种颜色的组合/阴影表示。通过这些,我们可以很容易地发现哪些任务处于背压状态(黑色)。背压任务下游最忙碌(红色)的任务很可能是背压的来源(瓶颈)。

单击一个特定任务并进入 ‘BackPressure’ Tab,我们可以进一步剖析问题并检查每个子任务的忙碌/背压/空闲状态。例如,如果存在数据倾斜且并非所有子任务都得到同等利用,这会特别方便。

在上面的例子中,我们可以清楚地看到哪些子任务处于空闲状态,哪些任务处于背压状态。坦率地说,这些足以让我们快速了解作业发生了什么,但是,还有一些细节值得解释。

2.1 那些数字表示什么?

如果你好奇底层是如何工作的,我们可以更深入一点。在这种新机制的基础上,我们有三个新指标,由每个子任务提供与计算:

  • idleTimeMsPerSecond
  • busyTimeMsPerSecond
  • backPressuredTimeMsPerSecond

上述指标分别测量子任务在空闲、忙碌以及背压状态下每秒花费的平均时间(以毫秒为单位)。除了一些四舍五入的误差外,加起来应该是 1000ms/s。本质上,它们与 CPU 使用率指标等非常相似。

另一个重要的细节是这些指标是短时间内(几秒钟)平均的值,并且考虑了子任务线程内发生的一切:算子、函数、计时器、Checkpoint、记录序列化/反序列化、网络堆栈以及其他 Flink 内部开销。忙于触发计时器并产生结果的 WindowOperator 会报告为忙碌或者背压。在 CheckpointedFunction#snapshotState 调用中执行一些代价比较大的函数,例如,刷新内部缓冲区,也会报告为忙碌。

但是,这里有一个限制:busyTimeMsPerSecond 和 idleTimeMsPerSecond 指标对子任务之外的单独线程中发生的任何事情都不敏感。目前存在如下两种场景:

  • 在算子中手动生成自定义线程(不鼓励的做法)。
  • 实现已经废弃的 SourceFunction 接口的 Source 代码。在此类 Source 下 busyTimeMsPerSecond 的值为 NaN/N/A。

为了在 Web UI 中显示这些原始数字,需要聚合所有子任务的这些指标(在作业图上,我们仅显示任务)。这就是为什么 Web UI 会显示给定任务所有子任务的最大值,以及为什么忙碌状态和背压状态的聚合最大值可能不会达到 100%。一个子任务可以在 60% 时处于背压状态,而另一个任务在 60% 时可能处于忙碌状态。导致任务在 60% 时既背压状态又处于忙碌状态。

2.2 动态负载

还有一件事。你还记得这些指标是在几秒钟内测量的平均值吗?请记住这一点,在分析具有动态负载的作业或任务时,例如,包含定期性触发的 WindowOperator 的(子)任务:恒定负载 50% 的子任务以及每秒在完全忙碌和完全空闲之间交替的子任务的 busyTimeMsPerSecond 值均为 500ms/s。此外,动态负载,尤其是触发窗口可以将瓶颈转移到作业图中的不同位置:

在此特定示例中,只要 SlidingWindowOperator 在累积消息,它就是瓶颈。但是,一旦开始触发其窗口(每 10 秒一次),下游任务 SlidingWindowCheckMapper -> Sink: SlidingWindowCheckPrintSink 就成为瓶颈,并且 SlidingWindowOperator 会受到背压。由于那些忙碌/背压/空闲指标是几秒钟内的平均时间,因此这种微妙变化不会立即可见。最重要的是,Web UI 每 10 秒才更新一次状态,使得发现这种频繁的变化变得更加困难。

3. 可以用背压做什么?

总的来说,这是一个复杂的话题,值得专门写一篇文章。在一定程度上,之前的博文中已经解决了这个问题。简而言之,有两种处理背压的方法:添加更多资源(更多机器、更快的 CPU、更多 RAM、更好的网络、使用 SSD……)或者优化现有资源的使用(优化代码、调整配置、避免数据倾斜)。无论哪种情况,我们首先需要通过以下方式分析导致背压的原因:

  • 判断有没有背压。
  • 定位导致背压的子任务或者机器。
  • 深入分析代码的哪一部分导致背压以及哪些部分资源不够。

背压监控的改进以及监控指标可以帮助我们解决前两点。为了解决最后一个问题,分析代码可能是一种可行的方法。为了帮助分析,同样从 Flink 1.13 开始,火焰图 被集成到 Flink 的 Web UI 中。火焰图 是一种众所周知的分析工具和可视化技术,值得尝试一下。

但请记住,在找到瓶颈所在后,可以像分析任何其他非分布式应用程序一样对其进行分析(通过检查资源利用率等)。对于此类问题,通常没有灵丹妙药。我们可以尝试扩容,但有时可能并不容易或不切实际。

无论如何,上述对背压监控的改进使我们能够轻松检测背压的来源,而火焰图可以帮助我们分析为什么这个子任务导致问题的原因。将这两个功能结合起来,可以使以前非常繁琐的 Flink 作业调试和性能分析过程变得更加容易!请升级到 Flink 1.13.x 并尝试一下!

欢迎关注我的公众号和博客:

原文:How to identify the source of backpressure?

赏几毛白!