ITPub博客

首页 > IT职业 > IT生活 > 详解 Flink 容器化环境下的 OOM Killed

详解 Flink 容器化环境下的 OOM Killed

IT生活 作者:seven123 时间:2021-01-27 20:58:56 0 删除 编辑

在生产环境中,Flink 通常会部署在 YARN 或 k8s 等资源管理系统之上,进程会以容器化(YARN 容器或 docker 等容器)的方式运行,其资源会受到资源管理系统的严格限制。另一方面,Flink 运行在 JVM 之上,而 JVM 与容器化环境并不是特别适配,尤其 JVM 复杂且可控性较弱的内存模型,容易导致进程因使用资源超标而被 kill 掉,造成 Flink 应用的不稳定甚至不可用。

针对这个问题,Flink 在 1.10 版本对内存管理模块进行了重构,设计了全新的内存参数。在大多数场景下 Flink 的内存模型和默认已经足够好用,可以帮用户屏蔽进程背后的复杂内存结构,然而一旦出现内存问题,问题的排查和修复都需要比较多的领域知识,通常令普通用户望而却步。

为此,本文将解析 JVM 和 Flink 的内存模型,并总结在工作中遇到和在社区交流中了解到的造成 Flink 内存使用超出容器限制的常见原因。由于 Flink 内存使用与用户代码、部署环境、各种依赖版本等因素都有紧密关系,本文主要讨论 on YARN 部署、Oracle JDK/OpenJDK 8、Flink 1.10+ 的情况。此外,特别感谢 @宋辛童(Flink 1.10+ 新内存架构的主要作者)和 @唐云(RocksDB StateBackend 专家)在社区的答疑,令笔者受益匪浅。

JVM 内存分区

对于大多数 Java 用户而言,日常开发中与 JVM Heap 打交道的频率远大于其他 JVM 内存分区,因此常把其他内存分区统称为 Off-Heap 内存。而对于 Flink 来说,内存超标问题通常来自 Off-Heap 内存,因此对 JVM 内存模型有更深入的理解是十分必要的。

除了上述 Spec 规定的标准分区,在具体实现上 JVM 常常还会加入一些额外的分区供进阶功能模块使用。以 HotSopt JVM 为例,根据 Oracle NMT[5] 的标准,我们可以将 JVM 内存细分为如下区域:

Heap: 各线程共享的内存区域,主要存放 new 操作符创建的对象,内存的释放由 GC 管理,可被用户代码或 JVM 本身使用。

Class: 类的元数据,对应 Spec 中的 Method Area (不含 Constant Pool),Java 8 中的 Metaspace。

Thread: 线程级别的内存区,对应 Spec 中的 PC Register、Stack 和 Natvive Stack 三者的总和。

Compiler: JIT (Just-In-Time) 编译器使用的内存。

Code Cache: 用于存储 JIT 编译器生成的代码的缓存。

GC: 垃圾回收器使用的内存。

Symbol: 存储 Symbol (比如字段名、方法签名、Interned String) 的内存,对应 Spec 中的 Constant Pool。

Arena Chunk: JVM 申请操作系统内存的临时缓存区。

NMT: NMT 自己使用的内存。

Internal: 其他不符合上述分类的内存,包括用户代码申请的 Native/Direct 内存。

Unknown: 无法分类的内存。

理想情况下,我们可以严格控制各分区内存的上限,来保证进程总体内存在容器限额之内。但是过于严格的管理会带来会有额外使用成本且缺乏灵活度,所以在实际中为了 JVM 只对其中几个暴露给用户使用的分区提供了硬性的上限,而其他分区则可以作为整体被视为 JVM 本身的内存消耗。

具体可以用于限制分区内存的 JVM 参数如下表所示(值得注意的是,业界对于 JVM Native 内存并没有准确的定义,本文的 Native 内存指的是 Off-Heap 内存中非 Direct 的部分,与 Native Non-Direct 可以互换)。

从表中可以看到,使用 Heap、Metaspace 和 Direct 内存都是比较安全的,但非 Direct 的 Native 内存情况则比较复杂,可能是 JVM 本身的一些内部使用(比如下文会提到的 MemberNameTable),也可能是用户代码引入的 JNI 依赖,还有可能是用户代码 自身通过 sun.misc.Unsafe 申请的 Native 内存。理论上讲,用户代码或第三方 lib 申请的 Native 内存需要用户来规划内存用量,而 Internal 的其余部分可以并入 JVM 本身的内存消耗。而实际上 Flink 的内存模型也遵循了类似的原则。

Flink TaskManager 内存模型

首先回顾下 Flink 1.10+ 的 TaskManager 内存模型。

显然,Flink 框架本身不仅会包含 JVM 管理的 Heap 内存,也会申请自己管理 Off-Heap 的 Native 和 Direct 内存。在笔者看来,Flink 对于 Off-Heap 内存的管理策略可以分为三种:

硬限制(Hard Limit): 硬限制的内存分区是 Self-Contained 的,Flink 会保证其用量不会超过设置的阈值(若内存不够则抛出类似 OOM 的异常)

软限制(Soft Limit): 软限制意味着内存使用长期会在阈值以下,但可能短暂地超过配置的阈值。

预留(Reserved): 预留意味着 Flink 不会限制分区内存的使用,只是在规划内存时预留一部分空间,但不能保证实际使用会不会超额。

结合 JVM 的内存管理来看,一个 Flink 内存分区的内存溢出会导致何种后果,判断逻辑如下:

1、若是 Flink 有硬限制的分区,Flink 会报该分区内存不足。否则进入下一步。
2、若该分区属于 JVM 管理的分区,在其实际值增长导致 JVM 分区也内存耗尽时,JVM 会报其所属的 JVM 分区的 OOM (比如 java.lang.OutOfMemoryError: Jave heap space)。否则进入下一步。
3、该分区内存持续溢出,最终导致进程总体内存超出容器内存限制。在开启严格资源控制的环境下,资源管理器(YARN/k8s 等)会 kill 掉该进程。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69989270/viewspace-2753923/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2020-11-24

  • 博文量
    17
  • 访问量
    9323