背压产生过程分析

在 Flink 运行过程中,每一个操作算子都会消费一个中间 / 过渡状态的流,并对它们进行转换,然后生产一个新的流。这种机制可以类比为:Flink 使用阻塞队列作为有界的缓冲区。跟 Java 里阻塞队列一样,一旦队列达到容量上限,处理速度较慢的消费者会阻塞生产者向队列发送新的消息或事件。下图展示了 Flink 中两个操作算子之间的数据传输以及如何感知到背压的:

image.png

首先,Source 中的事件进入 Flink 并被操作算子 1 处理且被序列化到 Buffer 中,然后操作算子 2 从这个 Buffer 中读出该事件。当操作算子 2 处理能力不足的时候,操作算子 1 中的数据便无法放入 Buffer,从而形成背压。背压出现的原因可能有以下两点:

  1. 下游算子处理能力不足
  2. 数据发生了倾斜

背压解决方案

实践中我们通过以下方式解决背压问题。

  • 缩短算子链合理合并算子,节省出资源。
  • 缩短算子链也会减少 Task(线程)之间的切换消息的序列化 / 反序列化及数据在缓冲区交换次数,进而提高系统整体吞吐量
  • 根据数据特性将不需要或者暂不需要的数**进行过滤,然后根据业务需求将数据分别处理**
    • 比如有些数据源需要实时处理,有些数据可以延迟的,
    • 合理利用 keyBy 算子 (多次Keyby 减轻数据倾斜)
    • 控制 Flink 时间窗口大小
    • 在上游算子处理逻辑中尽量合并更多数据来达到降低下游算子的处理压力

产生背压的原因

  • async I/O 没有设置 同时最大异步请求数

    限制并发请求的数量可确保Operator 不会积累未决请求的不断增长的积压,但是一旦容量用尽,它将触发背压。


背压介绍

什么是背压问题

  • 流系统中消息的处理速度跟不上消息的发送速度,会导致消息的堆积
  • 许多日常问题都会导致背压
    • 垃圾回收卡顿可能会导致流入的数据快速堆积
    • 一个数据源可能生产数据的速度过快
  • 背压如果不能得到正确地处理,可能会导致 资源被耗尽 或者甚至出现更糟的情况导致数据丢失

在同一时间点,不管是流处理job还是sink,如果有1秒的卡顿,那么将导致至少500万条记录的积压。换句话说,source可能会产生一个脉冲,在一秒内数据的生产速度突然翻倍。


举例说明

1、正常情况

  • 消息处理速度 >= 消息的发送速度,不发生消息拥堵,系统运行流畅

1568990970528.png
2、异常情况

  • 消息处理速度< 消息的发送速度,发生了消息拥堵,系统运行不畅。

1568990999476.png

背压问题解决方案

可以采取三种方案:

  • 将拥堵的消息直接删除
    • 会导致数据丢失,许多流处理程序而言是不可接受的
  • 将缓冲区持久化,以方便在处理失败的情况下进行数据重放
    • 会导致缓冲区积压的数据越来越多
  • 将拥堵的消息缓存起来,并告知消息发送者减缓消息发送的速度
    • 对source进行限流来适配整个pipeline中最慢组件的速度,从而获得稳定状态

Flink如何解决背压问题

  • Flink内部自动实现数据流自然降速,而无需担心数据丢失
  • Flink所获取的最大吞吐量是由 pipeline 中最慢的组件决定


Flink解决背压问题的原理

1569018265032.png
1、TaskManager(TM)启动时,会初始化网络缓冲池(NetworkBufferPool)

  • 默认生成 2048 个内存块(MemorySegment)
  • 网络缓冲池是Task之间共享的

2、Task线程启动时,Flink 会为Task的 Input Gate(IG)和 ResultPartion(RS)分别创建一个 LocationBufferPool

  • LocationBufferPool的内存数量由Flink分配
  • 为了系统更容易应对瞬时压力,内存数量是动态分配的

3、Task线程执行时,Netty接收端接收到数据时,为了将数据保存拷贝到Task中

  • Task线程需要向本地缓冲池(LocalBufferPool)申请内存
  • 若本地缓冲池没有可用内存,则继续向网络缓冲池(NetworkBufferPool)申请内存
  • 内存申请成功,则开始从Netty中拷贝数据
  • 若缓冲池已申请的数量达到上限,或网络缓冲池(NetworkerBufferPool)也没有可用内存时,该Task的Netty Channel会暂停读取,上游的发送端会立即响应停止发送,Flink流系统进入反压状态

4、经过 Task 处理后,由 Task 写入到 ResultPartion(RS)中

  • 当Task线程写数据到ResultPartion(RS)时,也会向网络缓冲池申请内存
  • 如果没有可用内存块,也会阻塞Task,暂停写入

5、Task处理完毕数据后,会将内存块交还给本地缓冲池(LocalBufferPool)

  • 如果本地缓冲池申请内存的数量超过池子设置的数量,将内存块回收给 网络缓冲池。如果没超过,会继续留在池子中,减少反复申请开销