Flink优化总结1
大数据之Flink优化总结
第1章 资源配置调优
Flink性能调优的第一步,就是为任务分配合适的资源,在一定范围内,增加资源的分配与性能的提升是成正比的,实现了最优的资源配置后,在此基础上再考虑进行后面论述的性能调优策略。
提交方式主要是yarn-per-job,资源的分配在使用脚本提交Flink任务时进行指定。
从1.11开始,增加了通用客户端模式(-t提交),参数使用-D <property=value>指定
bin/flink run \
-t yarn-per-job \
-d \
-p 5 \ 指定并行度
-Dyarn.application.queue=test \ 指定yarn队列
-Djobmanager.memory.process.size=1024mb \ 指定JM的总进程大小
-Dtaskmanager.memory.process.size=1024mb \ 指定每个TM的总进程大小
-Dtaskmanager.numberOfTaskSlots=2 \ 指定每个TM的slot数
-c com.atguigu.flink.tuning.UvDemo \
/opt/module/flink-1.13.1/myjar/flink-tuning-1.0-SNAPSHOT.jar
参数列表:
https://ci.apache.org/projects/flink/flink-docs-release-1.13/deployment/config.html
内存设置
TaskManager内存模型
1、内存模型详解
- JVM 特定内存:JVM本身使用的内存,包含JVM的metaspace和over-head
1)JVM metaspace:JVM元空间
taskmanager.memory.jvm-metaspace.size,默认256mb
2)JVM over-head执行开销:JVM执行时自身所需要的内容,包括线程堆栈、IO、编译缓存等所使用的内存。
taskmanager.memory.jvm-overhead.fraction,默认0.1
taskmanager.memory.jvm-overhead.min,默认192mb
taskmanager.memory.jvm-overhead.max,默认1gb
总进程内存*fraction,如果小于配置的min(默认192mb)(或大于配置的max(默认1gb))大小,则使用min或max当中的某个配置参数。
- 框架内存:Flink框架,即TaskManager本身所占用的内存,不计入Slot的资源中。
堆内:taskmanager.memory.framework.heap.size,默认128MB
堆外:taskmanager.memory.framework.off-heap.size,默认128MB
堆内:taskmanager.memory.task.heap.size,默认none,由Flink内存(不含JVM相关内存)扣除掉其他部分的内存得到。
堆外: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大小
堆外:taskmanager.memory.managed.fraction,默认0.4
taskmanager.memory.managed.size,默认none
如果size没指定,则等于Flink内存*fraction
2、案例分析
基于Yarn模式,一般参数指定的是总进程内存,taskmanager.memory.process.size,比如指定为4G,每一块内存得到大小如下:
(1)计算Flink内存
JVM元空间(JVM metaspace)256m
JVM执行开销(JVM over-head): 4g*0.1=409.6m,在[192m,1g]之间,最终结果409.6m
Flink内存=4g-256m-409.6m=3430.4m
(2)网络内存(Network)=3430.4m*0.1=343.04m,在[64m,1g]之间,最终结果343.04m
(3)托管内存(Managed Memory)=3430.4m*0.4=1372.16m (1.34GB)
(4)框架内存(Framework Heap、Framework Off-Heap),堆内和堆外都是128m
(5)Task堆内内存(Task Heap)=
3430.4m-128m-128m-343.04m-1372.16m=1459.2m(1.42GB)
所以进程内存给多大,每一部分内存需不需要调整,可以看内存的使用率来调整。
如上,如需优化内存,可以看到管理内存managed memory 的大小占用为0,主要是给K-V类型的状态来用(rocksDB),若未开启状态不为rocksDB,则会一直未0,考虑可以将该部分内存减少。
生产资源配置示例
bin/flink run \
-t yarn-per-job \
-d \
-p 5 \ 指定并行度
-Dyarn.application.queue=test \ 指定yarn队列
-Djobmanager.memory.process.size=2048mb \ JM 2~4G足够(一般不用调整后跟随变化)
-Dtaskmanager.memory.process.size=4096mb \ 单个TM 2~8G足够
-Dtaskmanager.numberOfTaskSlots=2 \ 与容器核数1core:1slot或2core:1slot
-c com.atguigu.flink.tuning.UvDemo \
/opt/module/flink-1.13.1/myjar/flink-tuning-1.0-SNAPSHOT.jar
Flink是实时流处理,关键在于资源情况能不能抗住高峰时期每秒的数据量,通常用QPS/TPS来描述数据情况。
合理利用cpu资源
Yarn的容量调度器默认情况下是使用“DefaultResourceCalculator”分配策略,只根据内存调度资源,不参考CPU情况,只会给到容器默认的最小核心数1,所以在Yarn的资源管理页面上看到每个容器的vcore个数还是1。
可以修改策略为 DominantResourceCalculator,该资源计算器在计算资源的时候会综合考虑cpu和内存的情况。在capacity-scheduler.xml 中修改属性:
<property>
<name>yarn.scheduler.capacity.resource-calculator</name>
<!-- <value>org.apache.hadoop.yarn.util.resource.DefaultResourceCalculator</value> -->
<value>org.apache.hadoop.yarn.util.resource.DominantResourceCalculator</value>
</property>
使用DefaultResourceCalculator 策略
bin/flink run \
-t yarn-per-job \
-d \
-p 5 \
-Drest.flamegraph.enabled=true \
-Dyarn.application.queue=test \
-Djobmanager.memory.process.size=1024mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dtaskmanager.numberOfTaskSlots=2 \
-c com.atguigu.flink.tuning.UvDemo \
/opt/module/flink-1.13.1/myjar/flink-tuning-1.0-SNAPSHOT.jar
可以看到一个容器只有一个vcore(容量调度):
4个容器:每个容器(单个TM需要一个容器)封装2个slot,指定的并行度为5,要运行该job则至少大于5个slot,因此开启的容器数至少为3(3 TM*2 Slot >= 5),加上JM本身要1个容器,因此一共需要4个容器。
4个Vcore:容器的线程数(核心数)。正常情况下,不指定参数yarn.containers.vcores,1个TM封装几个slot,容器就会自动申请几个Vcore,因此单个TM需要2个Vcore,3个TM则需6个,加上JM至少1个,则应申请7(3*2+1)个Vcore(公平调度器)。当为容量调度器且为默认的DefaultResourceCalculator策略时则为4。
使用DominantResourceCalculator策略
修改后yarn配置后,分发配置并重启yarn,再次提交flink作业:
bin/flink run \
-t yarn-per-job \
-d \
-p 5 \
-Drest.flamegraph.enabled=true \
-Dyarn.application.queue=test \
-Djobmanager.memory.process.size=1024mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dtaskmanager.numberOfTaskSlots=2 \
-c com.atguigu.flink.tuning.UvDemo \
/opt/module/flink-1.13.1/myjar/flink-tuning-1.0-SNAPSHOT.jar
看到容器的vcore数变了:
JobManager1个,占用1个容器,vcore=1
TaskManager3个,占用3个容器,每个容器vcore=2,总vcore=2*3=6,因为默认单个容器的vcore数=单TM的slot数。
注意:
刚启动yarn提交job后,默认先开启JM,此时的容器及vcore为1:1。
随后开启TM,此时增加3个容器及6个vcore,故一共有4:7。
正常情况下,不指定参数yarn.containers.vcores ,1个TM封装几个slot,,容器就会自动申请几个Vcore,因此单个TM需要2个Vcore,3个TM则需6个,加上JM至少1个容器和1个vcore,则应申请4个容器、7(3*2+1)个Vcore。
使用DominantResourceCalculator策略并指定容器vcore数
指定yarn容器的vcore数,提交:
bin/flink run \
-t yarn-per-job \
-d \
-p 5 \
-Drest.flamegraph.enabled=true \
-Dyarn.application.queue=test \
-Dyarn.containers.vcores=3 \ #不要超出yarn最大单个容器核心数
-Djobmanager.memory.process.size=1024mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dtaskmanager.numberOfTaskSlots=2 \
-c com.atguigu.flink.tuning.UvDemo \
/opt/module/flink-1.13.1/myjar/flink-tuning-1.0-SNAPSHOT.jar
JobManager1个,占用1个容器,vcore=1
TaskManager3个,占用3个容器,每个容器vcore =3,总vcore=3*3=9。
Slot只会隔离内存,不会隔离CPU。并且Slot的共享机制也会允许1个slot可以同时进行多个task,故单个slot不会只使用1个vcore(1个线程)。
并行度设置
设置方式:(优先级依次提高)
全局并行度计算
开发完成后,先进行压测。任务并行度给10以下,测试单个并行度的处理上限(source端单个并行度的处理速率)。然后 总QPS(以百万日活电商为例:高峰时一般20M/s或2W条/s)/单并行度的处理能力(高峰时测试到能造成反压时的处理速度) = 并行度
开发完Flink作业,压测的方式很简单,先在kafka中积压数据,之后开启Flink任务,出现反压,就是处理瓶颈。相当于水库先积水,一下子泄洪。
不能只从QPS去得出并行度,因为有些字段少、逻辑简单的任务,单并行度一秒处理几万条数据。而有些数据字段多,处理逻辑复杂,单并行度一秒只能处理1000条数据。
最好根据高峰期的QPS压测,并行度*1.2倍,富余一些资源。
查看UI:查看出现反压状态的算子任务。
查看metric:看得出source端的瓶颈单并行度在7000-8000条/s。
综上,当生产环境高峰期的QPS为2W/s时,并行度的可设置为:4-6。每条数据的复杂程序可能不同,全局并行度的设置可能达不到复杂场景时可进行更为精细度配置。
Source 端并行度的配置
数据源端是 Kafka,Source的并行度设置为Kafka对应Topic的分区数。避免因source并行度小于kafka分区数,当单个source对应消费分区数据不定(有的source可能消费一个分区,有的可能多个)时可能造成的数据倾斜(非聚合状态下都可能倾斜且消费速率不同)问题。
如果已经等于 Kafka 的分区数,消费速度仍跟不上数据生产速度,考虑下Kafka 要扩大分区,同时调大并行度等于分区数。
Flink 的一个并行度可以处理一至多个分区的数据,如果并行度多于 Kafka 的分区数,那么就会造成有的并行度空闲,浪费资源。
Transform端并行度的配置
- Keyby之前的算子
一般不会做太重的操作,都是比如map、filter、flatmap等处理较快的算子,并行度可以和source保持一致。
- Keyby之后的算子
如process算子。如果并发较大,建议设置并行度为 2 的整数次幂,例如:128、256、512;
小并发任务的并行度不一定需要设置成 2 的整数次幂;
大并发任务如果没有 KeyBy,并行度也无需设置为 2 的整数次幂;
Sink 端并行度的配置
Sink 端是数据流向下游的地方,可以根据 Sink 端的数据量及下游的服务抗压能力进行评估。如果Sink端是Kafka,可以设为Kafka对应Topic的分区数。
Sink 端的数据量小,比较常见的就是监控告警的场景,并行度可以设置的小一些。
Source 端的数据量是最小的,拿到 Source 端流过来的数据后做了细粒度的拆分,数据量不断的增加,到 Sink 端的数据量就非常大。那么在 Sink 到下游的存储中间件的时候就需要提高并行度。
另外 Sink 端要与下游的服务进行交互,并行度还得根据下游的服务抗压能力来设置,如果在 Flink Sink 这端的数据量过大的话,且 Sink 处并行度也设置的很大,但下游的服务完全撑不住这么大的并发写入,可能会造成下游服务直接被写挂,所以最终还是要在 Sink 处的并行度做一定的权衡。
第2章 状态及Checkpoint调优
RocksDB大状态调优
RocksDB 是基于 LSM Tree 实现的(类似HBase),写数据都是先缓存到内存中,所以RocksDB 的写请求效率比较高。RocksDB 使用内存结合磁盘的方式来存储数据,每次获取数据时,先从内存中 blockcache 中查找,如果内存中没有再去磁盘中查询。使用 RocksDB 时,状态大小仅受可用磁盘空间量的限制,性能瓶颈主要在于 RocksDB 对磁盘的读请求,每次读写操作都必须对数据进行反序列化或者序列化。当处理性能不够时,仅需要横向扩展并行度即可提高整个Job 的吞吐量。
从Flink1.10开始,Flink默认将RocksDB的内存大小配置为每个task slot的托管内存。调试内存性能的问题主要是通过调整配置项taskmanager.memory.managed.size 或者 taskmanager.memory.managed.fraction以增加Flink的托管内存(即堆外的托管内存)。进一步可以调整一些参数进行高级性能调优,这些参数也可以在应用程序中通过RocksDBStateBackend.setRocksDBOptions(RocksDBOptionsFactory)指定。下面介绍提高资源利用率的几个重要配置:
开启State访问性能监控
Flink 1.13 中引入了 State 访问的性能监控,即 latency trackig state。此功能不局限于 State Backend 的类型,自定义实现的 State Backend 也可以复用此功能。
State 访问的性能监控会产生一定的性能影响,所以,默认每 100 次做一次取样 (sample),对不同的 State Backend 性能损失影响不同:
对于 RocksDB State Backend,性能损失大概在 1% 左右
对于 Heap State Backend,性能损失最多可达 10%
state.backend.latency-track.keyed-state-enabled:true #启用访问状态的性能监控(默认false)
state.backend.latency-track.sample-interval: 100 #采样间隔
state.backend.latency-track.history-size: 128 #保留的采样数据个数,越大越精确
state.backend.latency-track.state-name-as-variable: true #将状态名作为变量
正常开启第一个参数即可(1.13后提交job时直接添加参数即可)。
bin/flink run \
-t yarn-per-job \
-d \
-p 5 \
-Drest.flamegraph.enabled=true \
-Dyarn.application.queue=test \
-Djobmanager.memory.process.size=1024mb \
-Dtaskmanager.memory.process.size=4096mb \
-Dtaskmanager.numberOfTaskSlots=2 \
-Dstate.backend.latency-track.keyed-state-enabled=true \
-c com.atguigu.flink.tuning.RocksdbTuning \
/opt/module/flink-1.13.1/myjar/flink-tuning-1.0-SNAPSHOT.jar
UI图可见:最大延迟为70000Ns(纳秒),一般当任务达到1s左右才考虑任务有产生延时。
开启增量检查点和本地恢复
1)开启增量检查点
RocksDB是目前唯一可用于支持有状态流处理应用程序增量检查点的状态后端,可以修改参数开启增量检查点:
state.backend.incremental: true #默认false,改为true。
或代码中指定
new EmbeddedRocksDBStateBackend(true)
2)开启本地恢复
当 Flink 任务失败时,可以基于本地的状态信息进行恢复任务,可能不需要从 hdfs 拉取数据。本地恢复目前仅涵盖键控类型的状态后端(RocksDB),MemoryStateBackend不支持本地恢复并忽略此选项。
state.backend.local-recovery: true
3)设置多目录
如果有多块磁盘,也可以考虑指定本地多目录(rocksDB本地保存的路径而非状态保存路径)
state.backend.rocksdb.localdir: /data1/flink/rocksdb,/data2/flink/rocksdb,/data3/flink/rocksdb
注意:不要配置单块磁盘的多个目录,务必将目录配置到多块不同的磁盘上,让多块磁盘来分担压力。
bin/flink run \
-t yarn-per-job \
-d \
-p 5 \
-Drest.flamegraph.enabled=true \
-Dyarn.application.queue=test \
-Djobmanager.memory.process.size=1024mb \
-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 com.atguigu.flink.tuning.RocksdbTuning \
/opt/module/flink-1.13.1/myjar/flink-tuning-1.0-SNAPSHOT.jar
全量状态下,单次ck状态的保存占用磁盘大小越来越大。(基于内存)
开启增量检查点(只能基于rocksDB),只有单次ck间隔保存的新增的状态大小。且每次ck间隔也变小了。因此,当rocksDB使用正确,做ck的性能不亚于内存。
调整预定义选项
Flink针对不同的设置为RocksDB提供了一些预定义的选项集合,其中包含了后续提到的一些参数,如果调整预定义选项后还达不到预期,再去调整后面的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缓存
整个 RocksDB 共享一个 block cache,读数据时内存的 cache 大小,该参数越大读数据时缓存命中率越高,默认大小为 8 MB,建议设置到 64 ~ 256 MB。
state.backend.rocksdb.block.cache-size: 64m #默认8m
增大write buffer和 level 阈值大小
RocksDB 中,每个 State 使用一个 Column Family,每个 Column Family 使用独占的 write buffer,默认64MB,建议调大。
调整这个参数通常要适当增加 L1 层的大小阈值max-size-level-base,默认256m。该值太小会造成能存放的SST文件过少,层级变多造成查找困难,太大会造成文件过多,合并困难。建议设为 target_file_size_base(默认64MB) 的倍数,且不能太小,例如5~10倍,即320~640MB。
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合并数
1)增大线程数
用于后台 flush 和合并 sst 文件的线程数,默认为 1,建议调大,机械硬盘用户可以改为 4 (1.13后默认为4)等更大的值
state.backend.rocksdb.thread.num: 4
2)增大writebuffer最小合并数
将数据从 writebuffer 中 flush 到磁盘时,需要合并的 writebuffer 最小数量,默认值为 1,可以调成3。
state.backend.rocksdb.writebuffer.number-to-merge: 3
开启分区索引功能
Flink 1.13 中对 RocksDB 增加了分区索引功能,复用了 RocksDB 的 partitioned Index & filter 功能,简单来说就是对 RocksDB 的 partitioned Index 做了多级索引。也就是将内存中的最上层常驻,下层根据需要再 load 回来,这样就大大降低了数据 Swap 竞争。线上测试中,相对于内存比较小的场景中,性能提升 10 倍左右。如果在内存管控下 Rocksdb 性能不如预期的话,这也能成为一个性能优化点。
state.backend.rocksdb.memory.partitioned-index-filters:true #默认false
参数设定案例
-t yarn-per-job \
-d \
-p 5 \
-Drest.flamegraph.enabled=true \
-Dyarn.application.queue=test \
-Djobmanager.memory.process.size=1024mb \
-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 com.atguigu.flink.tuning.RocksdbTuning \
/opt/module/flink-1.13.1/myjar/flink-tuning-1.0-SNAPSHOT.jar
Checkpoint设置
一般需求,我们的 Checkpoint 时间间隔可以设置为分钟级别(1 ~5 分钟)。对于状态很大的任务每次 Checkpoint 访问 HDFS 比较耗时,可以设置为 5~10 分钟一次Checkpoint,并且调大两次 Checkpoint 之间的暂停间隔,例如设置两次Checkpoint 之间至少暂停 4或8 分钟。同时,也需要考虑时效性的要求,需要在时效性和性能之间做一个平衡,如果时效性要求高,结合end- to-end时长,设置秒级或毫秒级。如果 Checkpoint 语义配置为 EXACTLY_ONCE,那么在 Checkpoint 过程中还会存在 barrier 对齐的过程,可以通过 Flink Web UI 的 Checkpoint 选项卡来查看 Checkpoint 过程中各阶段的耗时情况,从而确定到底是哪个阶段导致 Checkpoint 时间过长然后针对性的解决问题。
RocksDB相关参数在前面已说明,可以在flink-conf.yaml指定,也可以在Job的代码中调用API单独指定,这里不再列出。
// 使⽤ RocksDBStateBackend 做为状态后端,并开启增量 Checkpoint
RocksDBStateBackend rocksDBStateBackend = new RocksDBStateBackend("hdfs://hadoop1:8020/flink/checkpoints", true);
env.setStateBackend(rocksDBStateBackend);
// 开启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(任务cancel后能自动从ck恢复)
checkpointConf.enableExternalizedCheckpoints(
CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);