Flink性能调优

目录

  • 资源配置调优
    • Task Manager内存模型
    • 分配CPU资源
    • 并行度设置
  • 状态及CheckPoint调优
    • RocksDB大状态调优
    • CheckPoint设置
  • 反压处理
  • 数据倾斜处理
  • Job优化
  • FlinkSQL调优
  • 常见故障排除

资源配置调优

资源配置概述

Flink性能调优的第一步,就是为任务分配合适的资源,在一定范围内,增加资源的分配与性能的提升是成正比的,实现了最优的资源配置后,在此基础上再考虑进行后面论述的性能调优策略。提交方式主要是yarn-per-job,资源的分配在使用脚本提交Flink任务时进行指定

官网可配置参数列表

Task Manager内存模型

Flink性能调优_第1张图片

Flink性能调优_第2张图片

JVM 元空间:taskmanager.memory.jvm-metaspace.size,默认256mb

JVM overhead执行开销:JVM执行时自身所需要的内容,包括线程堆栈、IO、编译缓存等所使用的内存

  • taskmanager.memory.jvm-overhead.fraction,默认0.1
  • taskmanager.memory.jvm-overhead.min,默认192mb
  • taskmanager.memory.jvm-overhead.max,默认1gb
  • 总进程内存*fraction,如果小于配置的min(或大于配置的max)大小,则使用min/max大小

框架内存:Flink框架,即TaskManager本身所占用的内存,不计入Slot的资源中。

  • 堆内:taskmanager.memory.framework.heap.size,默认128MB
  • 堆外:taskmanager.memory.framework.off-heap.size,默认128MB

Task内存:Task执行用户代码时所使用的内存

  • 堆内:taskmanager.memory.task.heap.size,默认none,由Flink内存扣除掉其他部分的内存得到
  • 堆外:taskmanager.memory.task.off-heap.size,默认0,表示不使用堆外内存

网络内存:网络数据交换所使用的堆外内存大小,如网络数据交换缓冲区

  • taskmanager.memory.network.fraction,默认0.1
  • taskmanager.memory.network.min,默认64mb
  • taskmanager.memory.network.max,默认1gb
  • Flink内存*fraction,如果小于配置的min(或大于配置的 max)大小,则使用min/max大小

托管内存:用于RocksDB State Backend的本地内存和批的排序、哈希表、缓存中间结果

  • taskmanager.memory.managed.fraction,默认0.4
  • taskmanager.memory.managed.size,默认none
  • 如果size没指定,则等于Flink内存*fraction

基于yarn模式,一般参数指定的是总进程内存,taskmanager.memory.process.size,指定为4G内存模型计算如下

JVM元空间:256M

JVM执行开销:4g*0.1=409.6m,在[192m,1g]之间,最终结果409.6m

fink内存=进程内存=4g-256m-409.6m=3430.4m

网络内存=3430.4m*0.1=343.04m,在[64m,1g]之间,最终结果343.04m

托管内存=3430.4m*0.4=1372.16m

框架内存,堆内和堆外都是 128m

Task堆内内存=3430.4m-128m-128m-343.04m-1372.16m=1459.2m

进程内存给多大,每一部分内存需不需要调整,可以看内存的使用率来调整

# export hadoop环境
export HADOOP_HOME='/opt/module/hadoop-3.1.3'
export HADOOP_CONF_DIR='/opt/module/hadoop-3.1.3/etc/hadoop/'
export HADOOP_CONFIG_DIR='/opt/module/hadoop-3.1.3/etc/hadoop/'
export HADOOP_CLASSPATH=`hadoop classpath`
# 指定并行度5,指定yarn队列为test,指定Job Manager大小为2G,指定Task Manager为4G,指定容器核数1core:1slot或2core:1slot,关闭classloader检查
bin/flink run \
-t yarn-per-job \
-d \
-p 5 \
-Dyarn.application.queue=test \
-Djobmanager.memory.process.size=2048mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dtaskmanager.numberOfTaskSlots=1 \
-c czs.study.flink.WordCount \
./flinkyarncluster-1.0-SNAPSHOT-jar-with-dependencies.jar

分配CPU资源

Yarn的容量调度器默认情况下使用的是DefaultResourceCalculator分配策略,只根据内存调度资源,所以在yarn的资源管理页面上看到每个容器的vcore个数是1,可以修改策略为DominantResourceCalculator,该资源计算器在计算资源的时候会综合考虑cpu和内存的情况。在capacity-scheduler.xml中修改属性

<property>
    <name>yarn.scheduler.capacity.resource-calculatorname>
    
    <value>org.apache.hadoop.yarn.util.resource.DominantResourceCalculatorvalue>
property>

image-20220214142953649

image-20220214143501372

DominantResourceCalculator策略指定容器vcore数及每个容器核数


bin/flink run \
-t yarn-per-job \
-d \
-p 1 \
-Djobmanager.memory.process.size=2048mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dyarn.containers.vcores=3 \
-Dtaskmanager.numberOfTaskSlots=4 \
-c czs.study.flink.WordCount \
./flinkyarncluster-1.0-SNAPSHOT-jar-with-dependencies.jar

image-20220214144040197

image-20220214144532859

并行度设置

任务并行度一般给到10以下,测试单个并行度的处理上限,然后总QPS/单并行度的处理能力 = 并行度一般根据高峰期的QPS进行压测,并行度*1.2倍,富于一些资源

source端并行度配置

如果数据源端是kafka,source的并行度设置为kafka对应topic的分区数,如果已经等于kafka的分区数,消费数据仍跟不上数据生产的速度,要扩大分区,同时调大并行度等于分区数。Flink的一个并行度可以处理一至多个分区的数据,如果并行度多于kafka的分区数,那么就会造成有的并行度空闲,浪费资源。

transform端并行度配置

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

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

sink端并行度的配置

sink端是数据向下游的地方,可以根据sink端的数据量及下游的服务抗压能力进行评估。如果sink端是kafka,可以设为kafka对应topic的分区数。sink端的数据量小,如监控告警的场景,并行度可以设置的小一些。source端的数据量是最小的,拿到source端流过来的数据后做了细粒度的拆分,数据量不断的增加,到sink端的数据量就非常大。那么在sink到下游的存储中间件的时候就需要提高并行度。另外sink端要与下游的服务进行交互,并行度还得根据下游的服务抗压能力来设置, 如果在flink sink这端的数据量过大的话,且sink处并行度也设置的很大,但下游的服务完全撑不住这么大的并发写入,可能会造成下游服务直接被写挂,所以最终还是要在sink处的并行度做一定的权衡。

状态及CheckPoint调优

RocksDB大状态调优

RocksDB是基于LSM Tree实现的(类似 Hbase),写数据都是先缓存到内存中,所以Rocks的写请求效率比较高。Rocks DB使用内存结合磁盘的方式来存储数据,每次获取数据时,先从内存中blockcache中查找,如果内存中没有再去磁盘中查询。使用RocksDB时,状态大小仅受可用磁盘空间里的限制,性能瓶颈主要在于RocksDB对磁盘的读请求,每次读写操作都必须对数据进行反序列化或者序列化。当处理性能不够时,仅需要横向扩展并行度即可提高整个Job的吞吐量。

Flink性能调优_第3张图片

从Flink1.10开始,Flink默认将RocksDB的内存大小配置为每个task slot的托管内存。调试内存性能的问题主要是通过调整配置项taskmanager.memory.managed.size或者taskmanager.memory.managed.fraction以增加Flink的托管内存(即堆外的托管内存)。进一步可以调整一些参数进行高级性能调优,这些参数也可以在应用程序中通过RocksDBStateBackend.setRocksDBOptions(RocksDBOptionsFactory)指定

开启State访问性能监控

Flink性能调优_第4张图片

Flink1.13中引入了State访问的性能监控,即latency trackig state。此功能不局限于State Backend的类型,自定义实现的State Backend也可以复用此功能

State访问的性能监控会产生一定的性能影响,所以,默认每100次做一次取样,对不同的State Backend性能损失影响不同,对于RocksDB类型的性能损失大概在1%,对于Heap类型的性能损失最多可达10%

state.backend.latency-track.keyed-state-enabled:true #启用访问状态的性能监控
state.backend.latency-track.sample-interval: 100 #采样间隔
state.backend.latency-track.history-size: 128 #保留的采样数据个数,越大越精确
state.backend.latency-track.state-name-as-variable: true #将状态名作为变量

正常开启第一个参数即可-Dstate.backend.latency-track.keyed-state-enabled=true

bin/flink run \
-t yarn-per-job \
-d \
-p 1 \
-Djobmanager.memory.process.size=2048mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dtaskmanager.numberOfTaskSlots=2 \
-Dstate.backend.latency-track.keyed-state-enabled=true \
-c czs.study.flink.WordCount \
./flinkyarncluster-1.0-SNAPSHOT-jar-with-dependencies.jar

开启增量检查点和本地恢复

开启增量检查点:Rocks DB是目前唯一可用于支持有状态流处理应用程序增量检查点的状态后端,可以修改参数开启增量检查点

state.backend.incremental: true # 默认 false,改为 true。
或代码中指定
new EmbeddedRocksDBStateBackend(true)

开启本地恢复:当Flink任务失败时,可以基于本地的状态信息进行恢复任务,可能不需要从hdfs拉取数据。本地恢复目前仅涵盖键控类型的状态后端(RocksDB),MemoryStateBackend不支持本地恢复并忽略此选项

state.backend.local-recovery: true

设置多目录:如果有多块磁盘,也可以考虑指定本地多目录。不要配置单块磁盘的多个目录,务必将目录配置到多块不同的磁盘上,让多块磁盘来分担压力

state.backend.rocksdb.localdir: /data1/flink/rocksdb,/data2/flink/rocksdb,/data3/flink/rocksdb
bin/flink run \
-t yarn-per-job \
-d \
-p 1 \
-Djobmanager.memory.process.size=2048mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dtaskmanager.numberOfTaskSlots=2 \
-Dstate.backend.incremental=true \
-Dstate.backend.local-recovery=true \
-Dstate.backend.latency-track.keyed-state-enabled=true \
-c czs.study.flink.WordCount \
./flinkyarncluster-1.0-SNAPSHOT-jar-with-dependencies.jar

调整预定义选项

Flink针对不同的设置为Rocks DB提供了一些预定义的选项集合,如果调整预定义选项后还达不到预期,再去调整block、writebuffer等参数

当前支持的预定义选项有DEFAULT、SPINNING_DISK_OPTIMIZED、SPINNING_DISK_OPTIMIZED_HIGH_MEM或FLASH_SSD_OPTIMIZED。有条件上SSD 的,可以指定为FLASH_SSD_OPTIMIZED

#设置为机械硬盘+内存模式
state.backend.rocksdb.predefined-options: SPINNING_DISK_OPTIMIZED_HIGH_MEM

增大block缓存

整个Rocks DB共享一个block cache,读数据时内存的cache大小,该参数越大读数据时缓存命中率越高,默认大小为8MB,建议设置到64-256MB

# 默认 8m
state.backend.rocksdb.block.cache-size: 64m

增大write buffer和level阈值大小

RocksDB中,每个State使用一个Column Family,每个Column Family使用独占的write buffer,默认64MB,建议调大。调整这个参数通常要适当增加level的大小阈值max-size-level-base,默认256m。该值太小会造成能存放的SST文件过少,层级变多造成查找困难,太大会造成文件过多,合并困难。建议设为target_file_size_base(默认64MB)的倍数,且不能太小,例如510倍,即320640MB

state.backend.rocksdb.writebuffer.size: 128m
state.backend.rocksdb.compaction.level.max-size-level-base: 320m

增大 write buffer数量

每个Column Family对应的writebuffer最大数量,这实际上是内存中“只读内存表“的最大数量,默认值是 2。对于机械磁盘来说,如果内存足够大,可以调大到5左右

state.backend.rocksdb.writebuffer.count: 5

增大后台线程数和write buffer合并数

增大线程数:用于后台flush和合并sst文件的线程数,默认为1,建议调大,机械硬盘用户可以改为4等更大的值

state.backend.rocksdb.thread.num: 4

增大write buffer最小合并数:将数据从write buffer中flush到磁盘时,需要合并的writebuffer最小数量,默认值为 1,可以调成3。

state.backend.rocksdb.writebuffer.number-to-merge: 3

开启分区索引功能

Flink1.13中对Rocks DB增加了分区索引功能,复用了RocksDB的partitioned Index & filter功能,简单来说就是对RocksDB的partitioned Index做了多级索引。也就是将内存中的最上层常驻,下层根据需要再load回来,这样就大大降低了数据Swap竞争。线上测试中,相对于内存比较小的场景中,性能提升10倍左右。如果在内存管控下Rocks DB性能不如预期的话,这也能成为一个性能优化点。

# 默认 false
state.backend.rocksdb.memory.partitioned-index-filters:true

调优参数设置

bin/flink run \
-t yarn-per-job \
-d \
-p 1 \
-Djobmanager.memory.process.size=2048mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dtaskmanager.numberOfTaskSlots=2 \
-Dstate.backend.incremental=true \
-Dstate.backend.local-recovery=true \
-Dstate.backend.rocksdb.predefined-options=SPINNING_DISK_OPTIMIZED_HIGH_MEM \
-Dstate.backend.rocksdb.block.cache-size=64m \
-Dstate.backend.rocksdb.writebuffer.size=128m \
-Dstate.backend.rocksdb.compaction.level.max-size-level-base=320m \
-Dstate.backend.rocksdb.writebuffer.count=5 \
-Dstate.backend.rocksdb.thread.num=4 \
-Dstate.backend.rocksdb.writebuffer.number-to-merge=3 \
-Dstate.backend.rocksdb.memory.partitioned-index-filters=true \
-Dstate.backend.latency-track.keyed-state-enabled=true \
-c czs.study.flink.WordCount \
./flinkyarncluster-1.0-SNAPSHOT-jar-with-dependencies.jar

CheckPoint设置

一般需求,Checkpoint时间间隔可以设置为分钟级别(15分钟)。对于状态很大的任务每次Checkpoint访问HDFS比较耗时,可以设置为510分钟一次,并且调大两次Checkpoint之间的暂停间隔,例如设置两次Checkpoint之间至少暂停4或8分钟。同时也需要考虑时效性的要求,需要在时效性和性能之间做一个平衡,如果时效性要求高,结合end- to-end时长,设置秒级或毫秒级。如果Checkpoint语义配置为EXACTLY_ONCE,那么在Checkpoint过程中还会存在barrier对齐的过程, 可以通过Flink Web UI的Checkpoint选项卡来查看Checkpoint过程中各阶段的耗 时情况,从而确定到底是哪个阶段导致Checkpoint时间过长然后针对性的解决问题。

// 使⽤ RocksDBStateBackend 做为状态后端,并开启增量 Checkpoint
env.setStateBackend(new RocksDBStateBackend("hdfs://hadoop1:8020/flink/checkpoints", true););
// 开启 Checkpoint,间隔为 3 分钟
env.enableCheckpointing(TimeUnit.MINUTES.toMillis(3));
// 配置 Checkpoint
CheckpointConfig checkpointConf = env.getCheckpointConfig();
checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 最小间隔 4 分钟
checkpointConf.setMinPauseBetweenCheckpoints(TimeUnit.MINUTES.toMillis(4));
// 超时时间 10 分钟
checkpointConf.setCheckpointTimeout(TimeUnit.MINUTES.toMillis(10));
// 保存 checkpoint
checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

反压处理

Flink中文社区反应介绍

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

利用Flink Web UI定位反压节点

Flink Web UI的反压监控提供了SubTask级别的反压监控,1.13版本以前是通过周期性对Task线程的栈信息采样,得到线程被阻塞在请求Buffer(意味着被下游队列阻塞)的频率来判断该节点是否处于反压状态。默认配置下,这个频率在0.1以下则为OK,0.1至0.5为 LOW,而超过0.5则为 HIGH。Flink1.13优化了反压检测的逻辑(使用基于任务Mailbox计时,而不在再于堆栈采样),并且重新实现了作业图的UI展示:Flink现在在UI上通过颜色和数值来展示繁忙和反压的程度

Flink性能调优_第5张图片

Flink性能调优_第6张图片

分析瓶颈算子

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

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

总体来看,如果我们找到第一个出现反压的节点,反压根源要么是就这个节点,要么是它紧接着的下游节点。通常来讲,第二种情况更常见。如果无法确定,还需要结合Metrics进一步判断。

利用Metrics定位

监控反压时会用到的Metrics主要和Channel接受端的Buffer使用率有关,最为有用的是以下几个Metrics

Metrics 描述
outPoolUsage 发送端Buffer的使用率
inPoolUsage 接收端Buffer的使用率
floatingBufferUsage 接收端Floating Buffer的使用率
exclusiveBufferUsage 接收端Exclusive Buffer的使用率

其中inPoolUsage=floatingBuffersUsage+exclusiveBuffersUsage

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

Flink性能调优_第7张图片

Flink1.9及以上版本,还可以根据floatingBuffersUsage/exclusiveBuffersUsage以及其上游Task的outPoolUsage来进行进一步的分析一个Subtask和其上游Subtask 的数据传输。在流量较大时,Channel的Exclusive Buffer可能会被写满,此时Flink会向Buffer Pool申请剩余的Floating Buffer。这些Floating Buffer属于备用Buffer。

Flink性能调优_第8张图片

floatingBuffersUsage为高,则表明反压正在传导至上游

同时exclusiveBuffersUsage为低,则表明可能有倾斜比如,floatingBuffersUsage高、exclusiveBuffersUsage低为有倾斜,因为少数channel占用了大部分的 Floating Buffer

查看是否数据倾斜

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

Flink性能调优_第9张图片

使用火焰图分析

如果不是数据倾斜,最常见的问题可能是用户代码的执行效率问题(频繁被阻塞或者性能问题)需要找到瓶颈算子中的哪部分计算逻辑消耗巨大。最有用的办法就是对TaskManager进行CPU profile,从中我们可以分析到Task Thread是否跑满一个CPU核:如果是的话要分析CPU主要花费在哪些函数里面;如果不是的话要看 Task Thread阻塞在哪里,可能是用户函数本身有些同步的调用,可能是checkpoint或者GC等系统活动导致的暂时系统暂停

开启火焰图功能:Flink 1.13直接在WebUI提供JVM的CPU火焰图,这将大大简化性能瓶颈的分析,默认是不开启的,需要修改参数

// 参数配置方式
rest.flamegraph.enabled: true #默认 false

// 或者启动时提交
-Drest.flamegraph.enabled=true \

Flink性能调优_第10张图片

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

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

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

火焰图的颜色没有特殊含义,看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题

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

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

  • 如果是Flink 1.13以前的版本,可以手动做火焰图:制作火焰图

分析GC情况

TaskManager的内存以及GC问题也可能会导致反压,包括TaskManager JVM各区内存不合理导致的频繁Full GC甚至失联。通常建议使用默认的G1垃圾回收器。可以通过打印GC日志(-XX:+PrintGCDetails)使用GC分析器(GCViewer工具)来验证是否处于这种情况

-Denv.java.opts="-XX:+PrintGCDetails -XX:+PrintGCDateStamps"

因为是on yarn模式,运行的节点一个一个找比较麻烦。可以打开WebUI,选择JobManager或者TaskManager,点击Stdout,即可看到GC日志,点击下载按钮即可将GC日志通过HTTP的方式下载下来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8X1iUPr-1644829454634)(https://gitee.com/czshh0628/blog-images/raw/master/image-20220214165141691.png)]

通过GC日志分析出单个Flink Taskmanager堆总大小、年轻代、老年代分配的内 存空间、Full GC 后老年代剩余大小等,相关指标定义可以去Github具体查看。

GCViewer 地址

Linux下分析:java -jar gcviewer_1.3.4.jar gc.log

Windows下分析:直接双击gcviewer_1.3.4.jar,打开GUI界面,选择gc的log打开

如果是Source端数据读取性能比较低或者Sink端写入性能较差,需要检查第三方组件是否遇到瓶颈,还有就是做维表join时的性能问题。异步io+热缓存来优化读写性能,先攒批再读写

数据倾斜处理

Job优化

FlinkSQL调优

常见故障排除

非法配置异常

Java堆空间异常

直接缓冲存储器异常

元空间异常

网络缓冲区数量不足

超出容器内存异常

Checkpoint失败

Checkpoint慢

Kafka 动态发现分区

Watermark 不更新

依赖冲突

超出文件描述符限制

脏数据导致数据转发失败

通讯超时

其他常见错误

Flink on YARN(下):常见问题与排查思路-阿里云开发者社区 (aliyun.com)

你可能感兴趣的:(flink,flink,java,大数据)