一、背景
基于Flink框架编码开发的作业,最终都是转化为Java进程运行,若是对作业数据量规模、资源配置、上下游组件交互等没有做好充分的评估,OOM问题是绕不过的一道坎。
在实际的测试与生产实践中,常常遇见的OOM问题主要包含堆内存溢出(Java Heap Space)与直接内存溢出(direct buffer memory),其他诸如栈溢出、元空间溢出在Flink场景下相对少见。
针对Java Heap Space问题,一般通过调大作业并行度、调大process/heap内存、优化jvm参数等方式来解决;本文主要结合实际生产案例,讲解direct buffer memory问题与解决。
二、问题现象
一个实时Flink(1.16.1)作业的数据链路为:Kafka+Flink+HBase,消费kafka数据后查询多张Hbase表进行过滤/关联/打宽,最后将数据聚合结果发送到Kafka与HBase。
有一天下游反馈未收到结果数据,但与源系统沟通是有数据下发的,另外Flink作业仍保持Running状态。
三、问题排查
1.查看作业运行指标监控发现:source算子有反压,topic分区consumer的lag一直在缓慢增长,checkpoint耗时也在逐步增长。
2.查看作业运行日志发现:下游反馈数据问题的一段时间内较为频繁的报错direct buffer memory,如下图:
四、问题分析
1.Flink内存模型分析
基于FLink内存模型,直接内存主要包含三块:
- FrameworkOff-Heap:分配给Flink框架本身运行使用,如Akka/RPC通信,与用户代码无关,大小相关固定(默认128M)
- Task Off-Heap:分配给用户任务(Task)执行使用,如AsyncI/0、第三方connector、udf等,与用户任务直接相关(默认不配置、为0,按需从MaxdirectMemony中使用)
- Network:主要缓存tm/算子间的数据传输缓冲、join/shuffle/反压相关数据缓存(默认为Flink内存*10%)
2.flink作业直接内存溢出的影响分析
- JVM全局直接内存耗尽:TM直接崩溃重启,达到重启次数作业失败停止;一般无具体的堆栈信息
- Network内存不足:TM直接崩溃重启,达到重启次数作业失败停止;一般堆栈信息包含network.buffer.NetworkBufferPool.allocateBuffer
- Task off-heap内存不足:属于Task级别子任务异常,Flink有容错机制基于cp对子任务进行重启,期间会阻塞上游数据、消费积压、吞吐量下降;一般堆栈信息包含fink-connector、xx-client或Async等
- Framework off-heap内存不足:TM直接崩溃重启,达到重启次数作业失败停止;一般堆栈信息包含akka.actor或Netty等
3.现象分析
基于上述分析,此次问题推测大概率是Task off-heap内存不足导致。
查看作业配置:TM-process内存为8G,Task Off-Heap显示配置为128M,其他基本按默认平台配置。
- Framework off-heap:默认分配128M,动态申请释放
- Task off-heap:手动配置128M,动态申请释放
- Network:(process memory-jvm overhead-jvm metaspace)*10%=(8G-819M-256M)*10%=712M,预分配固定池
任务的最大直接内存值若没有显示指定,为上面3块内存之和:MaxDirectMemorySize=968M,查看作业启动日志也能佐证。
同时查看作业的直接内存使用情况如下:
从上述作业报错日志分析,作业在关联访问hbase时,nio.bytebuffer.allocatedirect()报直接内存溢出;
结合任务的直接内存使用大小监控分析:直接内存使用量频繁达到最大值,在750M与最大值间进行内存分配与回收(712M网络内存属于预分配固定使用)。
基于上述日志分析与监控展示,结合flink-Hbase connector使用任务堆外内存的技术特性,作业配置的直接内存无法满足关联查询HBase使用的内存资源,导致direct buffer memory。因为是task级别子任务异常,Flink自身的容错机制会基于checkpoint与最小region影响范围内进行task重启,期间会阻塞上游数据、消费积压、吞吐量下降,下游数据自然处理延迟。
五、解决方案
1.调大taskmanager.memory.task.off-heap.size,增加Flink框架与HBase交互使用的直接内存资源,避免内存不足
2.显示设置-XX:MaxDirectMemorySize,根据测试资源占有情况,冗余部分直接内存资源,做好双层保障
3.及时配置好Flink作业运行的内存资源占用监控与指标告警,若触及风险阈值,人工干预评估,提前处置应对。
针对此次问题,主要将taskmanager.memory.task.off-heap.size调大至512M,MaxDirectMemorySize也会随之增大。完成参数紧急调整上线后,在Flink webui、启动日志、监控面板中能看到明显效果:
1.task.off-heap内存提升至512M,MaxDirectMemorySize提升至1352M
2.任务直接内存使用量最大达到1.2G
3.任务处理吞吐量明显提升,并逐步追上消费积压
六、总结
Flink拥有自身的内存模型设计,对自身使用到的各类内存资源都有所考虑与保障,但在实际实时场景的需求分析、设计、开发过程中,往往会与上下游其他组件进行集成交互,其中涉及的资源开销与参数配置就得靠我们研发人员综合考虑。
Flink作业的稳定运行,除了对Flink框架自身设计原理需要有一定掌握外,还需要对三方集成的connector等外部依赖的运行机制有所了解,方能做到运行异常的抽丝剥茧,精准定位与优化。