Flink任务问题分析与性能调优

Flink任务问题分析与性能调优

作者: 吴培坚——虎牙实时计算平台研发工程师

1 性能分析:

Flink调优对于问题的定性很重要,只有先确定问题性质才能针对性优化。首先要明白,Flink是分布式流计算框架,可简单理解为多个相互通讯的有状态java进程,其调优本质跟普通的java程序大同小异。

1.1 问题定位的基础:

只有具备良好的的监控数据支持,才能感知问题/异常的发生并对其快速定位。

监控指标主要分为以下三个维度:

  1. Flink框架: 框架本身内嵌了很多方便运维调优的统计信息,极大方便了性能问题的定位。如:日志、反压系数、数据partition策略、数据传输指标、gc信息、延迟指标…
  2. 系统指标:操作系统本身的指标信息。如:oomkilled频率(容器环境)、内存用量、cpu负载、磁盘网络I/O…
  3. 进程/线程信息:TaskManager进程内的运行时信息,各线程算力负载信息、线程调度信息、Rocksdb线程负载…

1.2 性能问题主要归为两类:

  • 稳定性: 此类一般是进程异常退出、jvm oom、容器节点内存溢出导致进程被操作系统杀死的oomkilled问题、容器pod驱逐、主机宕机等导致节点丢失任务频繁重启。
  • 处理性能: 由于资源不足、i/o阻塞、程序逻辑等,导致的计算任务处理性能低下。稳定性低的任务往往也会有处理性能问题。

2 如何定位

2.1 稳定性问题:

稳定性问题非常直观,最终影响就是导致任务频繁的重启,这里只例举一些代表性原因:

原因及确定方式:

  1. 内存使用超出节点规格触发oomkilled。查看节点oomkilled记录,从节点内存指标可看出内存使用量达到100%后回落。 一般是内存不足、Rocksdb托管的内存溢出。
  2. TaskManager进程由于程序错误退出:查看具体丢失的taskmanager日志,可以看到TaskManager进程退出且正常关闭资源。 一般是程序代码不够鲁棒、内存配置问题导致。
  3. GC导致心跳超时:查看GC指标,GC日志。
  4. k8s驱逐:上述排查无异,查看k8s驱逐记录。 宿主机宕机、高负载节点驱逐(一般出现在高峰期)

如下图示Pod内存使用在1处达到100%,2显示TaskManager进程被操作系统kill,在3处删除pod后无监控点。这种都是节点出现oomkilled。

出现oomkilled时候,用户会收到告警。同时在"任务仪盘表页面-任务分析模块",会显示最近一天统计的oomkilled次数。

  • 如何判定是Rocksdb导致内存溢出?
    通过jemalloc分析可以看到大部分内存占用都消耗在Rocksdb的未压缩block上,基本上可以确定为Rocksdb导致。

在这里插入图片描述

在这里插入图片描述

2.2 吞吐性能问题:

吞吐性能未达到期望要求,可表现为:数据积压、任务出现反压、idleTimeMsPerSecond指标持续为0、checkpoint超时失败。

2.2.1 主要原因:

  1. 资源不足。CPU、网络带宽、磁盘/网络IO、磁盘容量都有可能成为瓶颈。通常情况都是CPU算力不够,表现为多个TaskManager长时间高负载;

  2. CPU资源无法跑满:

    1. TaskManager之间负载(数据或者Task)不均衡,表现为部分TaskManager高负载,部分相对空闲;
    2. Task线程高负载:不同Task的处理能力往往各不相同。资源充足的情况下,单线程处理瓶颈只在CPU的单核处理能力上,因此短板Task将会成为整个Flink任务的性能瓶颈。指标上表现为TaskManager整体利用率不高,但单Task处理线程负载达到100%;
  3. CPU资源非正常损耗,由于Full-GC、Rocksdb Compaction/Flush异常等导致的对Flink Task线程CPU资源的抢占。

  4. Rocksdb状态读写延迟,表现为Task线程在Rocksdb读写(get/write)上高负载。可通过Rocksdb日志分析;1.13以上版本可通过开启state-access-latency-tracking指标进行采样。

2.2.2 如何定位

Tips: 在非高峰期可以通过,减少处理并发(增加单并发数据量)、减少NetworkBufferPool(提高反压敏感度),测试任务的高峰处理能力。

  1. 定位短板Task:可通过 反压、idleTimeMsPerSecond 指标定位任务性能瓶颈所在的Task, 若Source Task不存在反压但数据存在积压,则Source Task为性能瓶颈(往往是数据【反】序列化操作)。
  2. 若短板Task所在TaskManager节点整体高负载,则考虑资源是否资源不足;
  3. 查看短板Task各个SubTask数据负载与反压系数,若数据负载与反压系数高度相关则考虑是否数据倾斜问题导致;
  4. 查看短板Task各个SubTask在各个TaskManager节点的分布情况与反压系数,若SubTask高度集中的节点,反压系数越高,则考虑是否Task不均衡导致(参照Flink调度策略优化:Task均衡中问题所示);
  5. 通过拆分Task中Chaining起来的Operator,查看短板Task线程堆栈负载等,定位到具体的执行方法。

3. 调优策略

3.1 吞吐性能调优

3.1.1 平衡数据分布:

  1. 使用Rescale/Rebalance代替Forward;
  2. 选择更为分散的(组合)字段用于keyby;
  3. 对key加盐、解盐处理;

3.1.2 平衡Task分布:

优化方式参照Flink调度策略优化:Task均衡

3.1.3 降低shuffle损耗

同一Task内的Flink算子数据是在线程内传输,不通Task之间的算子往往都是走网络传输(同个TaskManager内走本地)。
尽量将算子chaining起来,减少跨网传输与数据序列化/反序列化损耗。

3.1.4 单一职责原则:

  1. 一个Flink任务由多个Task之间的SubTask组成,一个线程执行一个Flink SubTask,上下游SubTask之间通过生产者-消费者模型进行数据传输。
    SubTask处理太慢会导致整个流程都延迟, 所以算子逻辑尽量简单,只做一件事(反例:在map中对list迭代、在filter中加载文件);
  2. 此外,由于一个线程执行一个Flink SubTask,Subtask的处理能力受限于单核,对于CPU密集的操作最好拆分到不同Task中充分利用多核CPU的处理能力.
  • eg: 很多用户实现SourceFunction时候除了拉取数据逻辑,还会对数据进行反序列化操作并提前过滤数据。
    这种会导致一个问题:当数据源来自消息中间件,假设topic分区数是4,source并行度是10,任务消费的时候最多只有4个Source Subtask进行数据处理。
    这时数据拉取任务与反序列化操作共享一个CU,无论资源如何扩,任务吞吐都不会有所提升。
  • 正确姿势: source只拉数据,rebalance到下游Task,在下游Task进行数据解析与清洗操作。这样下游算子才能利用到扩容带来的资源。

3.1.5 状态读写优化:

参照 3.3.4

3.2 稳定性提高

根本目的是提高节点稳定性,降低taskManager的丢失导致任务重启频率

3.2.1 容器环境问题:

  1. 宿主机宕机无法避免,对于数据延迟敏感的任务,建议冗余一两个空跑节点,以便任务快速恢复。
  2. 宿主机因负载均衡主动驱逐:pod设置为有状态、无状态pod提高优先级降低pod驱逐概率; pod驱逐策略优化,引入冷却时间,避免对相同任务的pod多次驱逐。

3.2.2 内存问题(oomkilled):

  1. Rocksdb memtables溢出,Rocksdb老版本对memtables部分的内存使用缺乏管控。
  2. Rocksdb iterator并发高的场景下锁定内存中的部分数据导致内存超用
应对方式:
  1. 扩容:横向扩容(扩大内存规格、提高 taskmanager.memory.managed.fraction 配比);纵向扩容(扩节点);
  2. 升级flink至1.12: memtables 内存不受管控,新版本rocksdb新增Write Buffer Manager,能有效限制memtables使用。
  3. 对于非时间域(没开窗)上的聚合操作,Flink不会清理自动状态,需要自行配置状态的过期时间。
  4. 使用jemalloc做为内存分配器。
  5. 减少在状态(RocksDB)上的迭代遍历操作。eg: 尽量使用增量计算(AggFuction) 替代 WindowProcessFunction。
  6. 本地磁盘使用SSD替换机械硬盘,RocksDB在SSD上有更好的性能;
  7. 增加jvm overheap/关闭rocksdb 内存托管: rocksdb iterator并发高的场景下锁定内存中的部分数据导致内存超用,增大预留空间给予超用。
  8. 开启rocksdb metrics,rocksdb 日志,精细化调整rocksdb配置: 平衡 【写放大<—>读放大<—>空间放大】三者

3.3.3 程序问题:

  1. FullGC频繁: 提高堆内存配比;dump下现场分析内存占用。
  2. TaskManager进程由于程序错误退出: 分析日志异常堆栈,养成良好编程习惯,不要吞异常信息。

3.3.4 Rocksdb调优

  • 非必须不要调整rocksdb参数:
  1. 使用默认的flink托管rocksdb内存可以满足大部分场景下的需求
  2. rocksdb参数调整比较复杂,调优需要对其内存模型与机制有清晰的了解,否者可能会越调性能越差
  3. 一旦调整了rocksdb参数,随着业务数据变化,往往参数配置也需要随之调整,会加重运维工作

什么情况下需要手动调整rocksdb内存呢:

  1. 3.2.2中 1-5 都无法解决内存oomkilled情况;
  2. 数据处理线程在Rocksdb读写上存在延迟;
		DefaultConfigurableOptionsFactory options = new DefaultConfigurableOptionsFactory(); // 重写这个类
        options.setDbLogDir("/data/rocksdb");  // 日志路径
        stateBackend.setRocksDBOptions(options);
  • Flink提供的RocksBD核心配置:

更多参数可看官网文档

| 含义 | 参数名 | 推荐值 | 默认值 |
| --- | --- | --- | --- |
|是否开启rocksdb内存托管|state.backend.rocksdb.memory.managed|
|rocksdb执行checkpoint线程数 | state.backend.rocksdb.checkpoint.transfer.thread.num | 非必要无需更改 | 
|flush/compaction 线程数 | state.backend.rocksdb.thread.num | 与Taskmanager core数一致 ||
|LSM动态分层| state.backend.rocksdb.compaction.level.use-dynamic-size |  |
|单个memtable大小|state.backend.rocksdb.writebuffer.size|  |
|memtable总个数 | state.backend.rocksdb.writebuffer.count |  |
|不可变memtable达到多少个开始合并 | state.backend.rocksdb.writebuffer.number-to-merge |  |
|block大小|state.backend.rocksdb.block.blocksize|||
|block cache(读缓冲)大小| state.backend.rocksdb.block.cache-size| ||
|单个sst文件大小|state.backend.rocksdb.compaction.level.target-file-size-base|| |
|首层最大size|state.backend.rocksdb.compaction.level.max-size-level-base|||

4 场景分析

4.1 状态读写阻塞

数据处理对状态读写频繁的任务比较容易出现这种问题。

4.1.1 现场分析:

任务做普通的数据清洗与窗口聚合操作,峰值数据量为100w/s,状态超过500GB,资源规格 45 * 【4core8gb】。

  1. 刚启动任务运行正常,一段时间后吞吐快速下降,checkpoint因为barriar阻塞在管道中延迟导致checkpoint异常,有些会出现内存溢出触发oomkilled。
  2. windowing aggregate function 上游task反压严重,
  3. taskManager节点整体cpu利用率不高(取决于数据倾斜程度),但存在个别节点cpu利用率不稳定
  4. 对cpu负载比较高的节点抽样分析,执行windowing aggregate function的线程cpu利用率接近100%,jstack分析堆栈长时间处于rocksdb读写操作
  5. 磁盘读/写不稳定,存在短时间大量磁盘读操作(压缩导致),且rocksdb:low 线程 负载高。

    rocksdb:low 为compaction线程,rocksdb:high为flush线程

  6. 查看rocksdb日志,发现存在Write Stalls。若使用了Flink1.13及以上版本可通过
    State Backends Latency Tracking Options 直观监控状态延迟

4.1.2 处理方案:

关闭rocksdb内存托管,避免内存溢出,只有关闭托管自定义rocksdb配置才能生效;
通过将memtables设为6个128mb,number-to-merge 设为3,来减少写放大;
state.backend.rocksdb.thread.num 设为4提高flush效率与压缩性能;
增大sst文件size与L0压缩阈值,降低压缩频率。

案例需要数据脱敏,整理完再做补充

猜你喜欢

转载自blog.csdn.net/qq_30708747/article/details/120307971