整体:https://segmentfault.com/a/11…
yarn 是 hadoop 的资源隔离
运行在 YARN 上带来的好处:
一个集群部署多个版本
计算资源按需伸缩
不同负载应用混搭,集群利用率高
共享底层存储,避免数据跨集群迁移
1. 到 RM 申请,在一个 Container 中启动 AM
包含异步的分配资源,暂存在缓冲区,等待 AM 来取
2.AM RPC 取资源并与 NM 通信
AM 领取资源后分配任务不是 yarn 事情,用户自行实现
3. 包含各种状态同步,比如 7, 持续的 nm 到 rm.
隔离算法
目前只能实现 cpu 和内存的隔离
1. 可以占用空闲资源 Capacity Scheduler
2. 平均分配 Fair Scheduler
基于 cgroups
linux/kernel/cgroup, 包含子系统:cpu,io,mmemory,net 等
内核中的代码在 kennel 下。
用户使用:通过文件系统配置(内核给用户提供的方法)
VFS 文件:ext2,ext3 磁盘,socket,cgroups。操作系统实现后可以通过 mount 挂载到 cgroups 文件系统
vi /etc/cgconfig.conf。/sys/fs/cgroup/cpuset 中配置即可
对于内存并没有直接用 cgroups
内存隔离:线程监控进程内存量,不是超过立刻杀死,有个生命期
- jvm 不足以:每个任务不仅有 java 进程,reduce 用 C ++
- 不能单纯的 cgroups 内存树直接配置
Linux 中所有的进程都是通过 fork() 复制来实现的,而为了减少创建进程带来的堆栈消耗和性能影响,Linux 使用了写时复制机制来快速创建进程。也就是说,一个子进程刚刚产生时,它的堆栈空间和父进程是完全一致的,那么从一开始它就拥有和父进程同样的 ru_maxrss,如果父进程的 ru_maxrss 比较大,那么由于 rusage 计算值取最大值,就算在触发写时复制后,子进程使用的实际最大驻留集大小被更新,我们获得的也还是父进程的那个值,也就是说我们永远拿不到子进程真实使用的内存。
Java 创建子进程时采用了“fork() + exec()”的方案,子进程启动瞬间,它的内存使用量与父进程是一致的,exec 系函数,这个系别的函数通过将当前进程的使用权转交给另一个程序,这时候进程原有的所有运行堆栈等数据将全部被销毁,因此 ru_maxrss 也会被清零计算, 然后子进程的内存会恢复正常;也就是说,Container(子进程)的创建过程中可能会出现内存使用量超过预先定义的上限值的情况(取决于父进程,也就是 NodeManager 的内存使用量);此时,如果使用 Cgroup 进行内存资源隔离,这个 Container 就可能会被“kill”。
虽然我们已经可以获得各个 Container 进程树的内存(物理内存、虚拟内存)使用量,但是我们不能仅凭进程树的内存使用量(物理内存、虚拟内存)是否超过上限值就决定是否“杀死”一个 Container,因为“子进程”的内存使用量是有“波动”的,为了避免“误杀”的情况出现,Hadoop 赋予每个进程“年龄”属性,并规定刚启动进程的年龄是 1,MonitoringThread 线程每更新一次,各个进程的年龄加一,在此基础上,选择被“杀死”的 Container 的标准如下:如果一个 Contaier 对应的进程树中所有进程(年龄大于 0)总内存(物理内存或虚拟内存)使用量超过上限值的两倍;或者所有年龄大于 1 的进程总内存(物理内存或虚拟内存)使用量超过上限值,则认为该 Container 使用内存超量,可以被“杀死”。(注意:这里的 Container 泛指 Container 进程树)
fork/exec/ 线程 / 进程在另一篇:xx
k8s
基本上完全基于 cgroups。但是对于内存 / 磁盘这种没有就不行的不可压缩资源,会再加一个阈值,防止不稳定,能分配的会少于这个。所以 k8s 对于内存的限制会在 fork 时误放大没有处理。
https://juejin.im/post/5b3d8c…