深刻了解 Flink 网络栈(二):监控、指标和处理背压

做者 | Nico Krube
译者 | 王强html

在以前的文章中,咱们从高级抽象到底层细节各个层面全面介绍了 Flink 网络栈的工做机制。做为这一系列的第二篇文章,本文将在第一篇的基础上更进一步,主要探讨如何监视与网络相关的指标,从而识别背压等因素带来的影响,或找出吞吐量和延迟的瓶颈所在。本文将简要介绍处理背压的手段,而以后的文章将进一步研究网络栈微调的话题。若是你不是很熟悉网络栈的知识,强烈建议先阅读本系列的第一篇文章 《原理解析 | 深刻了解 Apache Flink 的网络协议栈》。java

监控

网络监控工做中最重要的环节可能就是监控背压了,所谓背压是指系统接收数据的速率高于其处理速度 [1]。这种现象将给发送者带来压力,而致使它的缘由可能有两种状况:apache

  • 接收器很慢。

这多是由于接收器自己就遇到了背压,因此没法以与发送方相同的速率继续处理数据;也有多是接收器由于垃圾回收工做、缺乏系统资源或 I/O 瓶颈而暂时卡住了。缓存

  • 网络通道很慢。

这种状况可能和接收器没有(直接)关系,咱们说这时是发送器遇到了背压,由于在同一台机器上运行的全部子任务共享的网络带宽可能供不该求了。请注意,除了 Flink 的网络栈以外可能还有其余网络用户,例如源(source)和汇(sink)、分布式文件系统(检查点、网络附加存储)、日志记录和指标监测等。咱们以前的一篇关于容量规划的文章(https://www.ververica.com/blog/how-to-size-your-apache-flink-cluster-general-guidelines)介绍了更多相关内容。网络

[1] 若是你不熟悉背压,不了解它与 Flink 的交互方式,建议阅读咱们在 2015 年发表的关于背压的文章(https://www.ververica.com/blog/how-flink-handles-backpressure)。并发

当背压出现时,它将一路向上游传导并最终到达你的源,还会减慢它们的速度。这自己并非一件坏事,只是代表你缺少足够的资源处理当前的负载。但你可能想要作一些改进,在不动用更多资源的前提下处理更高的负载。为此你须要找到(1)瓶颈在哪里(位于哪一个任务 / 操做符)和(2)产生瓶颈的缘由。Flink 提供了两种识别瓶颈的机制:oracle

  • 直接经过 Flink 的 Web UI 及其背压监视器识别
  • 间接经过一些网络指标识别。

Flink 的 Web UI 大概是快速排除故障时的首选,但它存在一些缺点,咱们将在下面解释。另外一方面,Flink 的网络指标更适合持续监控和推断是哪些瓶颈致使了背压,并分析这些瓶颈的本质属性。咱们将在下文中具体介绍这两个部分。在这两种状况下,你都须要从全部的源和汇中找出背压的根源。调查工做的起点通常来讲是最后一个承受背压的操做符;并且最后这个操做符极可能就是背压产生的源头。dom

背压监视器

背压监视器只暴露在 Flink 的 WebUI[2] 中。因为它是仅在请求时才会触发的活动组件,所以目前没法经过监控指标来提供给用户。背压监视器经过 Thread.getStackTrace() 对 TaskManager 上运行的全部任务线程采样,并计算缓存请求中阻塞任务的样本数。这些任务之因此会阻塞,要么是由于它们没法按照网络缓冲区生成的速率发送这些缓存,要么就是下游任务处理它们的速度很慢,没法保证发送的速率。背压监视器将显示阻塞请求与总请求的比率。因为某些背压被认为是正常 / 临时的,因此监视器将显示如下状态:分布式

  • OK,比率 ≤ 0.10
  • LOW,0.10 < 比率 ≤ 0.5
  • HIGH,0.5 < 比率 ≤ 1

虽然说你也能够调整刷新间隔、样本数或样本之间的延迟等参数,但一般状况下这些参数用不着你来调整,由于默认值提供的结果已经够好了。ide

1

[2] 你还能够经过 REST API 访问背压监视器:/jobs/:jobid/vertices/:vertexid/backpressure

背压监视器能够帮助你找到背压源自何处(位于哪一个任务 / 操做符)。但你无法用它进一步推断背压产生的缘由。此外,对于较大的做业或较高的并行度来讲,背压监视器显示的信息就太乱了,很难分析,还可能要花些时间才能完整收集来自 TaskManager 的数据。另请注意,采样工做可能还会影响你当前做业的性能。

网络指标

网络指标和任务 I/O 指标比背压监视器更轻量一些,并且会针对当前运行的每一个做业不断更新。咱们能够利用这些指标得到更多信息,收集到的信息除了用来监测背压外还有其余用途。和用户关系最大的指标有:

  • Flink 1.8 及更早版本:outPoolUsage、inPoolUsage。它们是对各个本地缓冲池中已用缓存与可用缓存的比率估计。在使用基于信用的流控制解析 Flink 1.5-1.8 中的 inPoolUsage 时,请注意它只与浮动缓存有关(独占缓存不算在缓冲池里)。
  • Flink 1.9 及更新版本:outPoolUsage、inPoolUsage、floatingBuffersUsage、exclusiveBuffersUsage
    它们是对各个本地缓冲池中已用缓存与可用缓存的比率估计。从 Flink 1.9 开始,inPoolUsage 是 floatingBuffersUsage 和 exclusiveBuffersUsage 的总和。
  • numRecordsOut、numRecordsIn。这两个指标都带有两个做用域:一个是运算符,另外一个是子任务。网络监视使用的是子任务做用域指标,并显示它已发送 / 接收的记录总数。你可能须要进一步研究这些数字来找出特定时间跨度内的记录数量,或使用等效的 PerSecond 指标。
  • numBytesOut、numBytesInLocal、numBytesInRemote。表示这个子任务从本地 / 远程源发出或读取的字节总数。也能够经过 PerSecond 指标获取。
  • numBuffersOut、numBuffersInLocal、numBuffersInRemote。与 numBytes 相似,但这里计算的是网络缓冲区的数量。

警告:为了完整起见,咱们将简要介绍 outputQueueLength 和 inputQueueLength 这两个指标。它们有点像 [out、in] PoolUsage 指标,但这两个指标分别显示的是发送方子任务的输出队列和接收方子任务的输入队列中的缓存数量。但想要推断缓存的准确数量是很难的,并且本地通道也有一个很微妙的特殊问题:因为本地输入通道没有本身的队列(它直接使用输出队列),所以通道的这个值始终为 0(参见 FLINK-12576,https://issues.apache.org/jira/browse/FLINK-12576);在只有本地输入通道的状况下 inputQueueLength = 0。

总的来讲,咱们不鼓励使用 outputQueueLength 和 inputQueueLength,由于它们的解析很大程度上取决于运算符当前的并行度以及独占缓存和浮动缓存的配置数量。相比之下,咱们建议使用各类 *PoolUsage 指标,它们会为用户提供更详尽的信息。

注意:若是你要推断缓存的使用率,请记住如下几点:

任何至少使用过一次的传出通道老是占用一个缓存(Flink 1.5 及更高版本)。

Flink 1.8 及较早版本:这个缓存(即便是空的!)老是在 backlog 中计 1,所以接收器试图为它保留一个浮动缓存区。

Flink 1.9 及以上版本:只有当一个缓存已准备好消费时才在 backlog 中计数,好比说它已满或已刷新时(请参阅 FLINK-11082)。

接收器仅在反序列化其中的最后一条记录后才释放接收的缓存。

后文会综合运用这些指标,以了解背压和资源的使用率 / 效率与吞吐量的关系。后面还会有一个独立的部分具体介绍与延迟相关的指标。

背压

有两组指标能够用来监测背压:它们分别是(本地)缓冲池使用率和输入 / 输出队列长度。这两种指标的粒度粗细各异,惋惜都不够全面,怎样解读这些指标也有不少说法。因为队列长度指标解读起来有一些先天困难,咱们将重点关注输入和输出池的使用率指标,该指标也提供了更多细节信息。

  • 若是一项子任务的 outPoolUsage 为 100%,则它正在经受背压。子任务是已经阻塞了,仍是仍在将记录写入网络缓冲区,取决于 RecordWriter 当前正在写入的缓存有没有写满。这与背压监视器显示的结果是不同的!
  • 当 inPoolUsage 为 100%时表示全部浮动缓存都分配给了通道,背压最终将传递到上游。这些浮动缓存处于如下任一状态中:因为一个独占缓存正被占用(远程输入通道一直在尝试维护 #exclusive buffer 的信用),这些浮动缓存被保留下来供未来在通道上使用;它们为一个发送器的 backlog 保留下来等待数据;它们可能包含数据并在输入通道中排队;或者它们可能包含数据并正由接收器的子任务读取(一次一个记录)。
  • Flink 1.8 及更早的版本:根据 FLINK-11082(https://issues.apache.org/jira/browse/FLINK-11082),即便在正常状况下 100% 的 inPoolUsage 也很常见。
  • Flink 1.9 及以上版本:若是 inPoolUsage 持续在 100%左右,这就是出现上游背压的强烈信号。

下表总结了全部组合及其解释。但请记住,背压多是次要的的或临时的(也就是无需查看),或者只出如今特定通道上,或是由特定 TaskManager 上的其余 JVM 进程(例如 GC、同步、I/O、资源短缺等)引发的,源头不是某个子任务。

outPoolUsage low outPoolUsage high
inPoolUsage low 正常 注意(产生背压,当前状态:上游暂未出现背压或已经解除背压)
inPoolUsage high (Flink 1.9+) 若是全部上游任务的 outPoolUsage 都很低,则只须要注意(可能最终会产生背压); 若是任何上游任务的 outPoolUsage 变高,则问题(可能在上游致使背压,还多是背压的源头) 问题(下游任务或网络出现背压,可能会向上游传递)

咱们甚至能够经过查看两个连续任务的子任务的网络指标来深刻了解背压产生的缘由:

  • 若是接收器任务的全部子任务的 inPoolUsage 值都很低,而且有任一上游子任务的 outPoolUsage 较高,则多是网络瓶颈致使了背压。因为网络是 TaskManager 的全部子任务共享的资源,所以瓶颈可能不是直接源自这个子任务,而是来自于各类并发操做,例如检查点、其余流、外部链接或同一台计算机上的其余 TaskManager/ 进程。
  • 背压也能够由一个任务的全部并行实例或单个任务实例引发。

第一种状况一般是由于任务正在执行一些应用到全部输入分区的耗时操做;后者一般是某种误差的结果,多是数据偏斜或资源可用性 / 分配误差。后文的“如何处理背压”一节中会介绍这两种状况下的应对措施。

Flink 1.9 及以上版本

  • 若是 floatingBuffersUsage 没到 100%,那么就不太可能存在背压。若是它达到了 100% 且全部上游任务都在承受背压,说明这个输入正在单个、部分或所有输入通道上承受背压。你可使用 exclusiveBuffersUsage 来区分这三种状况:
    假设 floatingBuffersUsage 接近 100%,则 exclusiveBuffersUsage 越高,输入通道承受的背压越大。在 exclusiveBuffersUsage 接近 100%的极端状况下,全部通道都在承受背压。
    • 下表总结了 exclusiveBuffersUsage、floatingBuffersUsage 和上游任务的 outPoolUsage 之间的关系,还比上表多了一个 inPoolUsage = floatingBuffersUsage + exclusiveBuffersUsage:
exclusiveBuffersUsage low exclusiveBuffersUsage high
floatingBuffersUsage low + 全部上游 outPoolUsage low 正常 [3]
floatingBuffersUsage low + 任一上游 outPoolUsage high 问题(多是网络瓶颈) [3]
floatingBuffersUsage high + 全部上游 outPoolUsage low 注意(最终只有一些输入通道出现背压) 注意(最终多数或所有输入通道出现背压)
floatingBuffersUsage high + 任一上游 outPoolUsage high 问题(只有一些输入通道在承受背压) 问题(多数或所有输入通道都在承受背压)

[3] 不该该出现这种状况

资源使用率 / 吞吐量

除了上面提到的各个指标的单独用法外,还有一些组合用法能够用来探究网络栈的深层情况:

  • 吞吐量较低时 outPoolUsage 值却常常接近 100%,但同时全部接收器的 inPoolUsage 都很低,这代表咱们的信用通知的往返时间(取决于你的网络延迟)过久,致使默认的独占缓存数量没法充分利用你的带宽。能够考虑增长每通道缓存参数或尝试禁用基于信用的流量控制。
  • numRecordsOut 和 numBytesOut 这个组合能够用来肯定序列化记录的平均大小,进而帮助你针对峰值场景作容量规划。
  • 若是要了解缓存填充率和输出刷新器的影响,能够考察 numBytesInRemote 与 numBuffersInRemote 的组合。在调整吞吐量(而不是延迟!)时,较低的缓存填充率可能意味着网络效率较低。在这种状况下请考虑增长缓存超时时间。请注意,在 Flink 1.8 和 1.9 中,numBuffersOut 仅在缓存快填满或某事件停用某缓存(例如一个检查点屏障)时才会增长,这个动做还可能滞后。还请注意,因为缓存是针对远程信道的优化技术,对本地信道影响有限,所以不须要在本地信道上考察缓存填充率。
  • 你还可使用 numBytesInLocal 和 numBytesInRemote 的组合区分本地与远程流量,但在大多数状况下没这个必要。

如何处理背压?

假设你肯定了背压的来源,也就是瓶颈所在,下一步就是分析为何会发生这种状况。下面咱们按照从基本到复杂的顺序列出了致使背压的一些潜在成因。咱们建议首先检查基本成因,而后再深刻研究更复杂的成因,不然就可能得出一些错误的结论。
另外回想一下,背压多是暂时的,多是因为负载高峰、检查点或做业重启时数据 backlog 待处理致使的结果。若是背压是暂时的,那么忽略它就好了。此外还要记住,分析和解决问题的过程可能会受到瓶颈自己的影响。话虽如此,这里仍是有几件事须要检查一下。

系统资源

首先,你应该检查受控机器的基本资源使用状况,如 CPU、网络或磁盘 I/O 等指标。若是某些资源在被所有或大量占用,你能够执行如下操做:

  1. 尝试优化你的代码。此时代码分析器是颇有用的。
  2. 调整这项资源的 Flink。
  3. 经过增长并行度和 / 或增长群集中的计算机数量来扩展资源。

垃圾收集

通常来讲,长时间的垃圾回收工做会引起性能问题。你能够打印 GC 调试日志(经过 -XX: +PrintGCDetails)或使用某些内存 /GC 分析器来验证你是否处于这种情况下。因为 GC 问题的处理与应用程序高度相关,而且独立于 Flink,所以咱们不会在此详细介绍(可参考 Oracle 的垃圾收集调整指南,https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html 或 Plumbr 的 Java 垃圾回收手册,https://plumbr.io/java-garbage-collection-handbook)。

CPU/ 线程瓶颈

若是 CPU 瓶颈来自于一个或几个线程,而整台机器的 CPU 使用率仍然相对较低,则 CPU 瓶颈可能就很难被发现了。例如,48 核计算机上的单个 CPU 线程瓶颈只会带来 2%的 CPU 使用率。能够考虑使用代码分析器,由于它们能够显示每一个线程的 CPU 使用状况,这样就能识别出热线程。

线程争用

与上面的 CPU/ 线程瓶颈问题相似,共享资源上较高的线程争用率可能会致使子任务瓶颈。仍是要请出 CPU 分析器,考虑查找用户代码中的同步开销 / 锁争用——虽然咱们应该避免在用户代码中添加同步性,这可能很危险!还能够考虑调查共享系统资源。例如,默认 JVM 的 SSL 实现能够从共享的 /dev/urandom 资源周围获取数据。

加载不均衡

若是你的瓶颈是由数据误差引发的,能够尝试将数据分区更改成几个独立的重键,或实现本地 / 预聚合来清除误差或减轻其影响。

除此以外还有不少状况。通常来讲,为了削弱瓶颈从而减小背压,首先要分析它发生的位置,而后找出缘由。最好从检查哪些资源处于充分利用状态开始入手。

延迟追踪

追踪各个可能环节出现的延迟是一个独立的话题。在本节中,咱们将重点关注 Flink 网络栈中的记录的等待时间——包括系统网络链接的状况。在吞吐量较低时,这些延迟会直接受输出刷新器的缓存超时参数的影响,或间接受任何应用程序代码延迟的影响。处理记录的时间比预期的要长或者(多个)计时器同时触发——并阻止接收器处理传入的记录——时,网络栈内后续记录的等待时间会大大延长。咱们强烈建议你将本身的指标添加到 Flink 做业中,以便更好地跟踪做业组件中的延迟,并更全面地了解延迟产生的缘由。

Flink 为追踪经过系统(用户代码以外)的记录延迟提供了一些支持。但默认状况下此功能被禁用(缘由参见下文!),必须用 metrics.latency.interval 或 ExecutionConfig #setLatencyTrackingInterval() 在 Flink 的配置中设置延迟追踪间隔才能启用此功能。启用后,Flink 将根据 metrics.latency.granularity 定义的粒度生成延迟直方图:

  • single:每一个操做符子任务有一个直方图
  • operator(默认值):源任务和操做符子任务的每一个组合有一个直方图
  • subtask:源子任务和操做符子任务的每一个组合有一个直方图(并行度翻了两番!)

这些指标经过特殊的“延迟标记”收集:每一个源子任务将按期发出包含其建立时间戳的特殊记录。而后,延迟标记与正常记录一块儿流动,不会在线路上或缓存队列中超过正常记录。可是,延迟标记不会进入应用程序逻辑,并会在那里超过正常记录。所以,延迟标记仅测量用户代码之间的等待时间,而不是完整的“端到端”延迟。但用户代码会间接影响这些等待时间!

因为 LatencyMarker 就像普通记录同样位于网络缓冲区中,它们也会因缓存已满而等待,或因缓存超时而刷新。当信道处于高负载时,网络缓冲区数据不会增长延迟。可是只要一个信道处于低负载状态,记录和延迟标记就会承受最多 buffer_timeout/2 的平均延迟。这个延迟会加到每一个链接子任务的网络链接上,在分析子任务的延迟指标时应该考虑这一点。

只要查看每一个子任务暴露的延迟追踪指标,例如在第 95 百分位,你就应该能识别出是哪些子任务在显著影响源到汇延迟,而后对其作针对性优化。

 

 

 

 

 

 

 

 

 

 

 

原文连接

本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索