•  
思考:1.为什么阿里和腾讯都AllIn flink?现状:1.腾讯下半年预计全部迁移,每秒处理日志2亿条,所有的storm作业都要迁移到flink2.阿里各大互联网公司都开始迁移实时作业到flink3.flink的背压设计也是他成为apache顶级项目的杀手锏,需要熟悉其运行原理

一文读懂Apache Flink™ 如何处理背压_缓冲池

前言

人们经常问我们Flink是如何处理背压效应的。答案很简单:Flink不使用任何复杂的机制,因为它不需要。它是一个纯粹的数据流引擎,能够优雅地响应背压。在这篇博客文章中,我们介绍了背压的问题。然后,我们将深入研究Flink的运行时如何在任务之间传输数据缓冲区,并展示流数据传输如何作为一种背压机制自然地翻倍。我们最后通过一个小实验证明了这一点。

什么是背压

像Flink这样的流媒体系统需要能够优雅地处理背压。背压是指系统接收数据的速度高于它在临时负载高峰期间处理数据的速度。很多日常情况都会造成背压。例如,垃圾收集停滞可能导致传入数据的构建,或者数据源可能会在发送数据的速度上出现峰值。如果处理不当,背压可能导致资源耗尽,甚至在最坏的情况下导致数据丢失。

让我们看一个简单的例子。假设与源数据流管道,一个流媒体的工作,和一个水槽处理数据的速度每秒500万个元素在其稳定状态如下所示(一个黑条代表100万个元素,图像是一个1秒的“快照”系统):

一文读懂Apache Flink™ 如何处理背压_数据丢失_02

在某个时候,流作业或接收器都有1秒的停顿,这会导致生成500万个以上的元素。或者,源可能有一个峰值,在一秒钟内以两倍的速度生成数据。

一文读懂Apache Flink™ 如何处理背压_flink_03

我们如何处理这样的情况?当然,可以删除这些元素。然而,对于许多流应用程序来说,数据丢失是不可接受的,因为它们只需要处理一次记录。额外的数据需要在某个地方进行缓冲。缓冲还应该是持久的,因为在失败的情况下,需要重新播放该数据,以防止数据丢失。理想情况下,这些数据应该被缓冲在一个持久的通道中(例如,如果源保证持久性,则源本身——Apache Kafka就是这种通道的一个主要例子)。理想的反应是对整个管道从sink到source进行“背压”,通过节流使source的速度调整到管道最慢的部分,达到稳定状态:

一文读懂Apache Flink™ 如何处理背压_缓冲池_04

Flink中背压机制

Flink运行时的构建块是操作符和流。每个操作符都在使用中间流,对它们应用转换,并生成新的流。描述这种网络机制的最佳类比是,Flink使用具有有限容量的有效分布式阻塞队列。与连接线程的Java常规阻塞队列一样,一旦队列的缓冲效果耗尽(有限制的容量),较慢的接收方就会减慢发送方的速度。

以下面两个任务之间的简单流程为例,说明Flink是如何实现背压的:

一文读懂Apache Flink™ 如何处理背压_序列化_05

  •  
1.记录“A”进入Flink并由Task 1处理2.记录被序列化成一个缓冲区3.这个缓冲区被传送到Task 2, Task 2然后从缓冲区读取记录

为了保证记录事件在Flink可以通行,需要保证有缓冲区可用。在Flink中,这些分布式队列是逻辑流,每个生成和使用的流都通过托管缓冲池来实现有限的容量。缓冲池是一组缓冲区,这些缓冲区在使用后将被回收。一般的想法很简单:从池中取出一个缓冲区,用数据填充它,在数据被使用之后,将缓冲区放回池中,在那里可以再次重用它。

这些池的大小在运行时动态变化。网络堆栈中的内存缓冲区数量(=队列的容量)定义了系统在不同的发送方/接收方速度下可以执行的缓冲量。Flink保证总是有足够的缓冲区来取得“一些进展”,但是这种进展的速度取决于用户程序和可用内存的数量。更多的内存意味着系统可以简单地缓冲掉某些瞬态背压(短周期、短GC)。更少的记忆意味着对背部压力有更多的即时反应

以上面的简单示例为例:Task 1在输出端有一个与之关联的缓冲池,Task 2在输入端有一个与之关联的缓冲池。如果有一个缓冲区可用来序列化A,我们就序列化它并分派缓冲区。

我们要看两个例子:

  •  
1.本地交换:如果task 1和task 2都在同一个工作节点(TaskManager)上运行,则可以直接将缓冲区传递给下一个任务。任务2一消耗完它,它就会被回收。如果task 2比task 1慢,那么缓冲区的回收速度将低于task 1的填充速度,从而导致task 1的速度变慢2.远程交换:如果task 1和task 2运行在不同的工作节点上,那么只要缓冲区在连接上(TCP通道),就可以回收它。在接收端,数据从连线复制到输入缓冲池的缓冲区。如果没有可用的缓冲区,则中断从TCP连接的读取。通过简单的水印机制,输出端永远不会在线路上放太多的数据。如果有足够的数据在传输中,我们将等待将更多的数据复制到线路,直到它低于阈值。这保证了传输中不会有太多的数据。如果接收端没有使用新数据(因为没有可用的缓冲区),这将减慢发送方的速度。

这种固定大小的池之间的简单缓冲区流使Flink具有健壮的背压机制,在这种机制中,任务生成数据的速度永远不会超过消耗数据的速度。

我们描述的在两个任务之间传输数据的机制自然地扩展到复杂的管道中,从而确保背压在整个管道中传播。

让我们来看看一个简单的实验,它显示了Flink在工作时背压的行为。我们运行一个简单的生产者-消费者流拓扑,其中任务在本地交换数据,我们在其中改变任务生成记录的速度。对于这个测试,由于表示原因,我们使用比默认值更少的内存使背压效果更明显。每个任务使用两个大小为4096字节的缓冲区。在通常的Flink部署中,任务将有更多更大大小的缓冲区,这只会提高性能。测试在单个JVM中运行,但是使用完整的Flink代码堆栈。

图中显示了生产(黄色)和消费(绿色)任务的平均吞吐量占所获得的最大吞吐量的百分比(我们在单个JVM中每秒达到了800万个元素),这两个任务随着时间的变化而变化。为了测量平均吞吐量,我们测量任务每5秒处理的记录数量。

一文读懂Apache Flink™ 如何处理背压_序列化_06

首先,我们以60%的全速运行生产任务(我们通过Thread.sleep()调用模拟慢速运行)。使用者以相同的速度处理数据,而不会被人为地减慢。然后我们将消耗任务的速度降低到30%。在这里,背压效应开始发挥作用,因为我们看到生产者也自然放缓到30%的全速。然后,我们停止了对消费者的人工减速,两个任务都达到了它们的最大吞吐量。我们再次将消费者的速度降低到其全速的30%,管道也会立即与生产者的速度降低到其全速的30%产生反应。最后,我们再次停止减速,两个任务都以100%的全速进行。总之,我们看到生产者和消费者在管道中遵循彼此的吞吐量,这是流管道中所需的行为。

总结

Flink和Kafka这样的持久源一起,可以让您立即免费处理背压,而不会丢失数据。Flink不需要特殊的机制来处理背压,因为Flink中的数据传输是背压机制的两倍。因此,Flink实现了管道中最慢部分所允许的最大吞吐量。

 

个人心得

  •  
什么情况会带来背压:1.上游数据激增,下游flink计算压力大,消费不及时2.数据倾斜,flink计算缓慢,数据打散聚合3.sink端接收压力过大,数据写入慢,比如写入es,mysql,可以异步写入减少等待时长