关于flink性能优化小结

431 阅读10分钟

flink的内存模型

内存占比:

Framework Heap:128M

Task Heap:剩余多少是多少 Managed Memory:占比 flink的0.4

Framework Off-Heap:128M

Task Off-Heap:0

Network:占比flink的0.1 阈值范围64M-1G

JVM Metaspace:256M JVM Overhead:占比0.1 阈值范围192M-1G

并行度的计算

开发完成后,先进行压测。任务并行度给 10 以下,测试单个并行度的处理上限。

总 QPS/单并行度的处理能力 = 并行度

开发完 Flink 作业,压测的方式很简单,先在 kafka 中积压数据,之后开启 Flink 任务, 出现反压,就是处理瓶颈。相当于水库先积水,一下子泄洪。

不能只从 QPS 去得出并行度,因为有些字段少、逻辑简单的任务,单并行度一秒处理 几万条数据。而有些数据字段多,处理逻辑复杂,单并行度一秒只能处理 1000 条数据。

最好根据高峰期的 QPS 压测,并行度*1.2 倍,富余一些资源。

Source并行度的配置

数据源端是 Kafka,Source 的并行度设置为 Kafka 对应 Topic 的分区数。如果已经等于 Kafka 的分区数,消费速度仍跟不上数据生产速度,考虑下Kafka 要扩大分区,同时调大并行度等于分区数。

Flink 的一个并行度可以处理一至多个分区的数据,如果并行度多于 Kafka 的分区数, 那么就会造成有的并行度空闲,浪费资源。

Transform端并行度的配置

Keyby 之前的算子

一般不会做太重的操作,都是比如 map、filter、flatmap 等处理较快的算子,并行度 可以和 source 保持一致。

Keyby 之后的算子

如果并发较大,建议设置并行度为 2 的整数次幂,例如:128、256、512; 小并发任务的并行度不一定需要设置成 2 的整数次幂; 大并发任务如果没有 KeyBy,并行度也无需设置为 2 的整数次幂;

Sink 端并行度的配置

Sink 端是数据流向下游的地方,可以根据 Sink 端的数据量及下游的服务抗压能力进 行评估。如果 Sink 端是 Kafka,可以设为 Kafka 对应 Topic 的分区数。

Sink 端的数据量小,比较常见的就是监控告警的场景,并行度可以设置的小一些。

Source 端的数据量是最小的,拿到 Source 端流过来的数据后做了细粒度的拆分,数据量不断的增加,到 Sink 端的数据量就非常大。那么在 Sink 到下游的存储中间件的时候 就需要提高并行度。

checkpoint的设置

checkpoint的设置需要考虑时效性的要求,需要在时效性和性能之间做一个平衡,如果时效性要求高,结合 end- to-end 时长,设置秒级或毫秒级。如果 Checkpoint 语义配置为 EXACTLY_ONCE,那么在 Checkpoint 过程中还会存在 barrier 对齐的过程, 可以通过 Flink Web UI 的 Checkpoint 选项卡来查看 Checkpoint 过程中各阶段的耗 时情况,从而确定到底是哪个阶段导致 Checkpoint 时间过长然后针对性的解决问题。

checkpoint一般需要设置两个参数:

(1)间隔: 分钟或者秒级 (根据需求定义)

(2)最小等待间隔:参考间隔 (建议一半)

其他参数:

超时时间:默认10分钟,参考间隔,结合end-to-end调整(目前我们时间为30s主要是考虑到kafka告警问题)

失败次数

保留ck

反压的处理

简单来说,Flink 拓扑中每个节点(Task)间的数据都以阻塞队列的方式传输,下游来 不及消费导致队列被占满后,上游的生产也会被阻塞,最终导致数据源的摄入被阻塞。

反压的危害

  1. 影响 checkpoint 时长:barrier 不会越过普通数据,数据处理被阻塞也会导致 checkpoint barrier 流经整个数据管道的时长变长,导致 checkpoint 总体时间(End to End Duration)变长。
  2. 影响 state 大小:barrier 对齐时,接受到较快的输入管道的 barrier 后,它后面数 据会被缓存起来但不处理,直到较慢的输入管道的 barrier 也到达,这些被缓存的数据会 被放到 state 里面,导致 checkpoint 变大。

这两个影响对于生产环境的作业来说是十分危险的,因为 checkpoint 是保证数据一 致性的关键,checkpoint 时间变长有可能导致 checkpoint 超时失败,而 state 大小同 样可能拖慢 checkpoint 甚至导致 OOM (使用 Heap-based StateBackend)或者物理 内存使用超出容器资源的稳定性问题。

定位反压问题

解决反压首先要做的是定位到造成反压的节点,排查的时候,先把 operator chain 禁 用,方便定位到具体算子。

  1. 通过Flink Web UI 的反压监控提供了 SubTask 级别的反压监控
  2. 分析瓶颈算子

如果处于反压状态,那么有两种可能性:

(1)该节点的发送速率跟不上它的产生数据速率。这一般会发生在一条输入多条输出的 Operator(比如 flatmap)。这种情况,该节点是反压的根源节点,它是从 Source Task 到 Sink Task 的第一个出现反压的节点。

(2)下游的节点接受速率较慢,通过反压机制限制了该节点的发送速率。这种情况, 需要继续排查下游节点,一直找到第一个为 OK 的一般就是根源节点。

  1. Metrics定位

监控反压时会用到的 Metrics 主要和 Channel 接受端的 Buffer 使用率有关

outPoolUsage发送端 Buffer 的使用率
inPoolUsage接收端 Buffer 的使用率
floatingBuffersUsage接收端 Floating Buffer 的使用率
exclusiveBuffersUsage接收端 Exclusive Buffer 的使用率

其中 inPoolUsage = floatingBuffersUsage + exclusiveBuffersUsage。

(1)根据指标分析反压

分析反压的大致思路是:如果一个 Subtask 的发送端 Buffer 占用率很高,则表明它 被下游反压限速了;如果一个 Subtask 的接受端 Buffer 占用很高,则表明它将反压传导 至上游。反压情况可以根据以下表格进行对号入座

outPoolUsage 低outPoolUsage 高
inPoolUsage 低正常被下游反压,处于临时情况 (还没传递到上游)/可能是反压的根源,一条输入 多条输出的场景
inPoolUsage 高如果上游所有 outPoolUsage 都是低,有可能最终可能导致反压(还没传递到上游)/如果上游的 outPoolUsage 是高,则为反压根源被下游反压

(2)可以进一步分析数据传输

根据 floatingBuffersUsage/exclusiveBuffersUsage 以 及其上游 Task 的 outPoolUsage 来进行进一步的分析一个 Subtask 和其上游 Subtask 的数据传输。

在流量较大时,Channel 的 Exclusive Buffer 可能会被写满,此时 Flink 会向 BufferPool 申请剩余的 Floating Buffer。这些 Floating Buffer 属于备用 Buffer。

exclusiveBuffersUsage 低exclusiveBuffersUsage 高
floatingBuffersUsage 低 所有上游 outPoolUsage 低正常
floatingBuffersUsage 低 上游某个 outPoolUsage 高潜在的网络瓶颈
floatingBuffersUsage 高 所有上游 outPoolUsage 低最终对部分 inputChannel 反压(正在传递)最终对大多数或所有inputChannel反压(正在传递)
floatingBuffersUsage 高 上游某个 outPoolUsage 高只对部分 inputChannel 反压对大多数或者所有inputChannel反压

反压的原因及处理

注意:反压可能是暂时的,可能是由于负载高峰、CheckPoint 或作业重启引起的数据 积压而导致反压。如果反压是暂时的,应该忽略它。另外,请记住,断断续续的反压会影响 我们分析和解决问题。

定位到反压节点后,分析造成原因的办法主要是观察 Task Thread。按照下面的顺序, 一步一步去排查。

查看数据是否有倾斜

在实践中,很多情况下的反压是由于数据倾斜造成的,这点我们可以通过 Web UI 各个 SubTask 的 Records Sent 和 Record Received 来确认,另外 Checkpoint detail 里不同 SubTask 的 State size 也是一个分析数据倾斜的有用指标。

使用火焰图分析

如果不是数据倾斜,最常见的问题可能是用户代码的执行效率问题(频繁被阻塞或者性 能问题),需要找到瓶颈算子中的哪部分计算逻辑消耗巨大。

最有用的办法就是对 TaskManager 进行 CPU profile,从中我们可以分析到 Task Thread 是否跑满一个 CPU 核:如果是的话要分析 CPU 主要花费在哪些函数里面;如果 不是的话要看 Task Thread 阻塞在哪里,可能是用户函数本身有些同步的调用,可能是 checkpoint 或者 GC 等系统活动导致的暂时系统暂停。

(1)开启火焰图 rest.flamegraph.enabled: true #默认 false

(2)查看火焰图

火焰图是通过对堆栈跟踪进行多次采样来构建的。每个方法调用都由一个条形表示,其 中条形的长度与其在样本中出现的次数成正比。

On-CPU: 处于 [RUNNABLE, NEW]状态的线程

Off-CPU: 处于 [TIMED_WAITING, WAITING, BLOCKED]的线程,用于查看在样本中发现的阻塞调用。

(3)分析火焰图

颜色没有特殊含义,具体查看:

纵向是调用链,从下往上,顶部就是正在执行的函数

横向是样本出现次数,可以理解为执行时长。

看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。

外部组件交互

如果发现我们的 Source 端数据读取性能比较低或者 Sink 端写入性能较差,需要检 查第三方组件是否遇到瓶颈,还有就是做维表 join 时的性能问题。

例如:

Kafka 集群是否需要扩容,Kafka 连接器是否并行度较低

HBase 的 rowkey 是否遇到热点问题,是否请求处理不过来 ClickHouse 并发能力较弱,是否达到瓶颈

......

关于第三方组件的性能问题,需要结合具体的组件来分析,最常用的思路:

1)异步 io+热缓存来优化读写性能

2)先攒批再读写

当前现有业务建议使用本地缓存+预加载维表+定时刷新 或者 本地缓存+定时刷新

数据的倾斜

判断是否数据倾斜

相同 Task 的多个 Subtask 中,个别 Subtask 接收到的数据量明显大于其他 Subtask 接收到的数据量,通过 Flink Web UI 可以精确地看到每个 Subtask 处理了多 少数据,即可判断出 Flink 任务是否存在数据倾斜。通常,数据倾斜也会引起反压。

另外, 有时 Checkpoint detail 里不同 SubTask 的 State size 也是一个分析数据倾 斜的有用指标。

使用localkeyby思想

在 keyBy 上游算子数据发送之前,首先在上游算子的本地对数据进行聚合后,再发送 到下游,使下游接收到的数据量大大减少,从而使得 keyBy 之后的聚合操作不再是任务的 瓶颈。类似 MapReduce 中 Combiner 的思想,但是这要求聚合操作必须是多条数据或 者一批数据才能聚合,单条数据没有办法通过聚合来减少数据量。从 Flink LocalKeyBy 实 现原理来讲,必然会存在一个积攒批次的过程,在上游算子中必须攒够一定的数据量,对这 些数据聚合后再发送到下游。

实现方式:

DataStreamAPI 需要自己写代码实现

SQL 可以指定参数,开启 miniBatch 和 LocalGlobal 功能