菠菜网

usdt无需实名(www.caibao.it):详解 Flink 容器化环境下的 OOM Killed

时间:2个月前   阅读:25

原题目:详解 Flink 容器化环境下的 OOM Killed

简介: 本文将剖析 JVM 和 Flink 的内存模子,并总结在工作中遇到和在社区交流中领会到的造成 Flink 内存使用超出容器限制的常见缘故原由。由于 Flink 内存使用与用户代码、部署环境、种种依赖版本等因素都有慎密关系,本文主要讨论 on YARN 部署、Oracle JDK/OpenJDK 8、Flink 1.10+ 的情形。

在生产环境中,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 专家)在社区的答疑,令笔者受益匪浅。

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

凭据 JVM 8 Spec[1],JVM 治理的内存分区如下图:

img1. JVM 8 内存模子

除了上述 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 内存模子。

img2. Flink 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 掉该历程。

为直观地展示 Flink 各内存分区与 JVM 内存分区间的关系,笔者整理了如下的内存分区映射表:

img3. Flink 分区及 JVM 分区内存限制关系

,

欧博电脑版

欢迎进入欧博电脑版(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

,

凭据之前的逻辑,在所有的 Flink 内存分区中,只有不是 Self-Contained 且所属 JVM 分区也没有内存硬限制参数的 JVM Overhead 是有可能导致历程被 OOM kill 掉的。作为一个预留给种种差别用途的内存的大杂烩,JVM Overhead 简直容易出问题,但同时它也可以作为一个兜底的隔离缓冲区,来缓解来自其他区域的内存问题。

举个例子,Flink 内存模子在盘算 Native Non-Direct 内存时有一个 trick:

Although, native non-direct memory usage can be accounted for as a part of the framework off-heap memory or task off-heap memory, it will result in a higher JVM’s direct memory limit in this case.

虽然 Task/Framework 的 Off-Heap 分区中可能含有 Native Non-Direct 内存,而这部门内存严酷来说属于 JVM Overhead,不会被 JVM -XX:MaxDirectMemorySize 参数所限制,但 Flink 照样将它算入 MaxDirectMemorySize 中。这部门预留的 Direct 内存配额不会被现实使用,以是可以留给没有上限 JVM Overhead 占用,到达为 Native Non-Direct 内存预留空间的效果。

OOM Killed 常见缘故原由

与上文剖析一致,实践中导致 OOM Killed 的常见缘故原由基本源于 Native 内存的泄露或者过分使用。由于虚拟内存的 OOM Killed 通过资源治理器的设置很容易制止且通常不会有太大问题,以是下文只讨论物理内存的 OOM Killed。

RocksDB Native 内存的不确定性

众所周知,RocksDB 通过 JNI 直接申请 Native 内存,并不受 Flink 的管控,以是现实上 Flink 通过设置 RocksDB 的内存参数间接影响其内存使用。然而,现在 Flink 是通过估算得出这些参数,并不是异常准确的值,其中有以下的几个缘故原由。

首先是部门内存难以准确盘算的问题。RocksDB 的内存占用有 4 个部门[6]:

● Block Cache: OS PageCache 之上的一层缓存,缓存未压缩的数据 Block。

● Indexes and filter blocks: 索引及布隆过滤器,用于优化读性能。

● Memtable: 类似写缓存。

● Blocks pinned by Iterator: 触发 RocksDB 遍历操作(好比遍历 RocksDBMapState 的所有 key)时,Iterator 在其生命周期内会阻止其引用到的 Block 和 Memtable 被释放,导致分外的内存占用[10]。

前三个区域的内存都是可设置的,但 Iterator 锁定的资源则要取决于应用营业使用模式,且没有提供一个硬限制,因此 Flink 在盘算 RocksDB StateBackend 内存时没有将这部门纳入思量。

其次是 RocksDB Block Cache 的一个 bug[8][9],它会导致 Cache 巨细无法严酷控制,有可能短时间内超出设置的内存容量,相当于软限制。

对于这个问题,通常我们只要调大 JVM Overhead 的阈值,让 Flink 预留更多内存即可,由于 RocksDB 的内存超额使用只是暂时的。

glibc Thread Arena 问题

另外一个常见的问题就是 glibc 著名的 64 MB 问题,它可能会导致 JVM 历程的内存使用大幅增进,最终被 YARN kill 掉。

详细来说,JVM 通过 glibc 申请内存,而为了提高内存分配效率和削减内存碎片,glibc 会维护称为 Arena 的内存池,包罗一个共享的 Main Arena 和线程级别的 Thread Arena。当一个线程需要申请内存但 Main Arena 已经被其他线程加锁时,glibc 会分配一个约莫 64 MB (64 位机械)的 Thread Arena 供线程使用。这些 Thread Arena 对于 JVM 是透明的,但会被算进历程的总体虚拟内存(VIRT)和物理内存(RSS)里。

默认情形下,Arena 的最大数目是 cpu 核数 * 8,对于一台通俗的 32 核服务器来说最多占用 16 GB,不可谓不可观。为了控制总体消耗内存的总量,glibc 提供了环境变量 MALLOC_ARENA_MAX 来限制 Arena 的总量,好比 Hadoop 就默认将这个值设置为 4。然而,这个参数只是一个软限制,所有 Arena 都被加锁时,glibc 仍会新建 Thread Arena 来分配内存[11],造成意外的内存使用。

通常来说,这个问题会泛起在需要频仍建立线程的应用里,好比 HDFS Client 会为每个正在写入的文件新建一个 DataStreamer 线程,以是对照容易遇到 Thread Arena 的问题。若是嫌疑你的 Flink 应用遇到这个问题,对照简朴的验证方式就是看历程的 pmap 是否存在许多巨细为 64MB 倍数的延续 anon 段,好比下图中蓝色几个的 65536 KB 的段就很有可能是 Arena。

img4. pmap 64 MB arena

这个问题的修复设施对照简朴,将 MALLOC_ARENA_MAX 设置为 1 即可,也就是禁用 Thread Arena 只使用 Main Arena。固然,这样的价值就是线程分配内存效率会降低。不外值得一提的是,使用 Flink 的历程环境变量参数(好比 containerized.taskmanager.env.MALLOC_ARENA_MAX=1)来笼罩默认的 MALLOC_ARENA_MAX 参数可能是不可行的,缘故原由是在非白名单变量(yarn.nodemanager.env-whitelist)冲突的情形下, NodeManager 会以合并 URL 的方式来合并原有的值和追加的值,最终造成 MALLOC_ARENA_MAX="4:1" 这样的效果。

最后,另有一个更彻底的可选解决方案,就是将 glibc 替换为 Google 家的 tcmalloc 或 Facebook 家的 jemalloc [12]。除了不会有 Thread Arena 问题,内存分配性能更好,碎片更少。在现实上,Flink 1.12 的官方镜像也将默认的内存分配器从 glibc 改为 jemelloc [17]。

JDK8 Native 内存泄露

Oracle Jdk8u152 之前的版本存在一个 Native 内存泄露的 bug[13],会造成 JVM 的 Internal 内存分区一直增进。

详细而言,JVM 会缓存字符串符号(Symbol)到方式(Method)、成员变量(Field)的映射对来加速查找,每对映射称为 MemberName,整个映射关系称为 MemeberNameTable,由 java.lang.invoke.MethodHandles 这个类卖力。在 Jdk8u152 之前,MemberNameTable 是使用 Native 内存的,因此一些过时的 MemberName 不会被 GC 自动清算,造成内存泄露。

要确认这个问题,需要通过 NMT 来查看 JVM 内存情形,好比笔者就遇到过线上一个 TaskManager 的跨越 400 MB 的 MemeberNameTable。

img5. JDK8 MemberNameTable Native 内存泄露

在 JDK-8013267[14] 以后,MemeberNameTable 从 Native 内存被移到 Java Heap 当中,修复了这个问题。然而,JVM 的 Native 内存泄露问题不止一个,好比 C2 编译器的内存泄露问题[15],以是对于跟笔者一样没有专门 JVM 团队的用户来说,升级到最新版本的 JDK 是修复问题的最好设施。

YARN mmap 内存算法

众所周知,YARN 会凭据 /proc/${pid} 下的历程信息来盘算整个 container 历程树的总体内存,但这里面有一个对照特殊的点是 mmap 的共享内存。mmap 内存会所有被算进历程的 VIRT,这点应该没有疑问,但关于 RSS 的盘算则有差别尺度。 依据 YARN 和 Linux smaps 的盘算规则,内存页(Pages)按两种尺度划分:

● Private Pages: 只有当前历程映射(mapped)的 Pages

● Shared Pages: 与其他历程共享的 Pages

● Clean Pages: 自从被映射后没有被修悔改的 Pages

● Dirty Pages: 自从被映射后已经被修悔改的 Pages

在默认的实现里,YARN 凭据 /proc/${pid}/status 来盘算总内存,所有的 Shared Pages 都会被算入历程的 RSS,即便这些 Pages 同时被多个历程映射[16],这会导致和现实操作系统物理内存的误差,有可能导致 Flink 历程被误杀(固然,条件是用户代码使用 mmap 且没有预留足够空间)。

为此,YARN 提供 yarn.nodemanager.container-monitor.procfs-tree.smaps-based-rss.enabled 设置选项,将其设置为 true 后,YARN 将凭据更准确的 /proc/${pid}/smap 来盘算内存占用,其中很要害的一个观点是 PSS。简朴来说,PSS 的差别点在于盘算内存时会将 Shared Pages 均分给所有使用这个 Pages 的历程,好比一个历程持有 1000 个 Private Pages 和 1000 个会分享给另外一个历程的 Shared Pages,那么该历程的总 Page 数就是 1500。 回到 YARN 的内存盘算上,历程 RSS 即是其映射的所有 Pages RSS 的总和。

在默认情形下,YARN 盘算一个 Page RSS 公式为: ``` Page RSS = Private_Clean + Private_Dirty + Shared_Clean + Shared_Dirty ``` 由于一个 Page 要么是 Private,要么是 Shared,且要么是 Clean 要么是 Dirty,以是实在上述公示右边有至少三项为 0 。而在开启 smaps 选项后,公式变为: ``` Page RSS = Min(Shared_Dirty, PSS) + Private_Clean + Private_Dirty ``` 简朴来说,新公式的效果就是去除了 Shared_Clean 部门被重复盘算的影响。

虽然开启基于 smaps 盘算的选项会让盘算加倍准确,但会引入遍历 Pages 盘算内存总和的开销,不如 直接取 /proc/${pid}/status 的统计数据快,因此若是遇到 mmap 的问题,照样推荐通过提高 Flink 的 JVM Overhead 分区容量来解决。

总结

本文首先先容 JVM 内存模子和 Flink TaskManager 内存模子,然后据此剖析得出历程 OOM Killed 通常源于 Native 内存泄露,最后枚举几个常见的 Native 内存泄露缘故原由以及处置设施,包罗 RocksDB 内存占用的不确定性、glibc 的 64MB 问题、JDK8 MemberNameTable 泄露和 YARN 对 mmap 内存盘算的不准确。由于笔者水平有限,不能保证所有内容均准确无误,若读者有差别意见,异常迎接留言指教一起探讨。

作者:林小铂

上一篇:usdt充币教程(www.6allbet.com):怪兽充电独家进驻百度科技园 助力打造功效完善的办公空间

下一篇:usdt无需实名买入卖出(www.caibao.it):高通将收购NUVIA 强化Snapdragon晶片竞争力

网友评论