将Flink Job提交到Yarn上运行,命令行中指定:-p 3 –yts 1,启动一个拥有3个TaskManager的Flink集群,3个TaskManager在Yarn中对应3个container。但是运行一段时间后发现其中某个Container会反复重新启动,如下图所示,Container的编号都到49了:
从TaskManager的Flink WebUi界面点击进去,查询到对应的Container的日志文件位置,在文件中找到了如下内容,发现Container由于内存超出,被Kill了。日志如下图所示:
从日志上看出是由于内存超过Container的上限导致Container被Kill了。但是测试环境数据量并不大,checkpoint的state看了下也只有50M,所以不太可能是由于Flink跑程序占用了太多的内存把Container给跑挂了。从process-tree日志上的内容来看,里面有很多python3的进程,这个是由于我在Flink中调用python算法,那么是不是由于这个算法调用导致的内存溢出?带着这个疑惑,来了解下Container内存管理的机制。
首先来了解下Linux中的父子进程机制,Linux中使用fork()生成一个子进程,进程占用的内存可以用top命令查看:
VIRT:进程占用的虚拟内存
RES:进程占用的物理内存
SHR:进程使用的共享内存
S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数
所以父子进程真正占用的总的物理内存为:RES-parent + RES-child - SHR
因为Container进程可能会创建子进程,所以Container监控的内存为:以Container进程为根的进程树中所有进程的内存(物理内存、虚拟内存)使用总量。
在Linux /proc目录下,有大量以整数命名的目录,这些整数是某个正在运行的进程的PID,而目录/proc/
综上所述,Yarn的内存资源隔离实际是内存使用量监控。
所以Flink Job的Container为什么会被Kill,因为Container调用了Python程序,虽然Python程序运行的时候使用的直接是操作系统内存,但是这部分内存Container也是能够通过进程树管理到的!而每个Slot中Python程序又是多线程运行的,所以占用的内存量很大,导致Container监测到超过内存上限被Kill。
了解到Container进程内存管理原理之后,通过降低多线程并行度以及增加Flink堆外内存比例解决了该问题。因为堆外内存配置大了,Flink的Job如果不需要那么多的话,也不会用掉。经过几轮测试之后,确定这个问题被解决!
如果子进程比父进程先结束,但是父进程没有显示调用wait或waitpid函数,会直到父进程结束时才会回收子进程的资源!这样的子进程就是僵尸进程。如果父进程先于子进程结束,子进程于是成为进程1(init进程)的子进程,这种进程就是孤儿进程。
子进程退出之后,内核会释放该进程所有的资源,例如打开的文件,占用的内存等 ,但是进程号会保留,直到父进程调用wait / waitpid来取时才释放。僵尸进程就是父进程没有调用wait / waitpid,导致进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
孤儿进程是没有父进程的进程,孤儿进程进程号释放的这个重任就落到了init进程身上。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。因此孤儿进程并不会有什么危害。
参考:
https://www.zhihu.com/question/57075260(Linux父子进程内存占用的关系)
https://www.cnblogs.com/lushilin/p/9401494.html(父子进程孤儿问题)
https://www.cnblogs.com/bravery/archive/2012/06/27/2560611.html(查看进程占用了多少内存)
https://www.cnblogs.com/candlia/p/11920128.html(Container内存监控机制)