解决Spark数据倾斜中8大棘手问题的实用方法
写在前面
本文主要分析 Linux 系统内存统计的一些指标以及进程角度内存使用监控的一些方法。
开始阅读这篇文章前,请先简单阅读下面的几篇文章。
《进程眼中的线性地址空间》
《线程眼中的线性地址空间》
《聊聊内r e 3 % O w存管理》
想必这几% / Q D 3 m 5篇文章f ` . : 8 ,过后,基本概念就不需要再赘述了。所以下文直接就找一台 Intel x86_64 架构下安装了 6G ) 0 B ^ & @4bit Linux 系统的服务器作为例进行相关的实验和结果分析。Linux 的内存管理从物理内存管理到虚拟内L } : : 2 ( W 6 d存管理涉及的概念和统计项实在太多,本文从实用和系统运维的角度出发,只列举一些最实用] _ w的统计。
从 free 命令开始
上面的背景介绍文章把内存相关的基础概念讲的差不多了 : a 9 R J,这里不再赘述。本文定位是内存统计,所以P n z $ 横向的统计项:
total — 系统总内存(9 K , 6其实就是从
/proc/meminfo获取的)used — 已使用内存
free — 未使用的内存
shared — 共享内存的大小,主要是
tmpfB 8 ? ` f r ~sbuff / cache —
buffers和cache使用的内存之@ s ~ r 5 `和available — 可用内存,可以简单理解为未使用的内存和可释放的内存之和(buffer、cache 可以释放大部分,所以这里近似等于 free + buff@ a ^ p U Fer / cache 的大小)
这台机器的系统和内核稍微新一点,这个输出可能和你看到的不一样,早先的h % P A j | I nfree命令的输出是这样:
这里的shared为u C , K D 2 A )0,| Q 0 g ~ o 2 .因为这台服务# ( S H器没用共享内存。这里多解释下-/+ buffer/cache这行,字面意思就是used - buffers/cache和used + buffe0 { 2 H ^ @rs/cacheQ : N } M h ! i。前者指的是从应用程序角度系统被用掉了多少内存,后者指的是从应用程序角度看系统还有多少内存能用。听起来很复杂,其实说白了就是因为R * v 5buffers和cachedf ] o = W可以被释放出来,多几个指标看看系统还能用多少内存而已。
下面用几个公式来解释这个输! h ? M l 7 I { X出:
1 | # 内存总量 = 已使用内存 + 空闲内存 |
buffers/cached不是100%都能释放出来使用的,上面的“可用内存”其实就是个近似值。最上面新版本系统的输出中有一个available项目表示可用内存,值小于free + buff/cache,H . _ U内核 3.14 之后支持该特性(虽然也不是绝对意义上的精确的可用内存大小,囧)。
这里i i { ? { 1 0 x w稍微多说一点buffers和cached。Linux 2; a f e d.4.10 内核之前,磁盘的缓存有两种,即Buffer Cache和Page Cache。前者缓存管理磁盘文件系统Y = 9 1时读取的块,后者存放访问具体文件内容时生成的页。在 2.4.10 之后,Buffer Cache这个概念m O + E Y P ) @ 8就不存在了,这些5 d 1 W Q J数据被放在Page Cache中(这种 z @Page被称为Buffs & h $ : } & (er Pages)。
简而言之,现在磁盘的 cache 只有 Page Cache一种,在Page Cache中,有一种Page叫Buffer Page,这种Page都与一个叫buffer_head的数据结构关联,这些页- ented状态,所以这里逐一列举出来常见的统计项解释一下。
首先明V * 0确一点,内核目前并+ 5 F @ h没有绝A ! N ^ 2 ,对精确的统计所有的内存使用量,比如alloc_pages接口申请的内存不一定被统计在内(除非所有调用alloc_pages的代码主动进行统计,如果某些不讲究的驱动程Q W | z m 序没有主动统计的话统计值就肯R ` R ; C )定对不上了)。
先看这三项全局统计:
Mf q ; . 0 @ r QemTotaU o 3 s , fl — 总的全局可用内存大小(即物理
RAM减去保留的以~ m : : f N J Y及内核代码占用的,系统启动后一般固定不变)MemFree — 总的全局未使用内存大小
MemAvailable — 内核估计出来的全局可用内存大小,非精确值(
MemFg _ /ree不代表所有可用的内存,Cache/Buffer、Slab均有部分可以临时释放的内存要计算在内)
用户进程的内存页分为两种:
与文件关联的内存页(
File-backed Pages), 比如程序文件、读取文件时数据对应的缓存页与文件无关的匿名内存页(
Anonymous Pages),比如进程的堆、栈等分配的内存
所有Page Cache里的页面都是Fi) c H a yle-backed Pages,File-backed Pagesa K H o X @在内存不足的时候可以直接写回对应的硬盘文件里,即Page-out。而Anonymous PagesD g t h x在内存不足时就只能写到硬盘上的交换区Swap里来释放内存,称之为Swap-out。
Anonymous Pages与用户进程共存,进程退出则Anonymous pages释放,而] t ? U Q 8Page CacD h W j l 6he即使y @ 8 } Y p d |在进程退出后还可以缓存。
下面是磁盘缓存相关的统计项:
Buffers — 块设备所占用的缓存页,{ I : P a W比如磁盘文件系统的
meta信息如SuperBlock等,直接读写块设备产生的缓存也统计在这里(例如dd命令)Cached — 从磁盘读取的文件内容缓存(即
Page cache)SwapCached —
Swap中包a J ) a a W U - N含的确定要被 ! o z X @换出,但是尚未写入物理交换区的匿名内存页SwapTotal — 可用的磁盘
Swap总大小SwapFree — 磁盘
Swap的free3 t m大小h , u p Y 8 j U 1Dirty — 修改了等待写回磁盘的内存大小
WriO & z Z . N v f .teback — 正在写回磁盘的内存I * I # i | % =大小
以下几项和内核的页面回收算法(Page FraS ? 6 / ! 6 2 ?me Reclaiming)相关,Page Cache和所有用户进程的内存(除内核栈和HugePages外)都在相关的LRU Lists上。内核在 2.6 以前就引入了增强的LRU算法| 0 d来解决朴素的LRU算法完全不考虑使用频率的问题。具体的Active 链表和Inactive 链表的使用详情请参阅其他资料。
Actir c vve — 最近使用的内存,回收的优先级低
Inactive — 最近较少使用的内存,回收的优先级高
Active (anon) —
Active 链表中的匿名页(Anonymous Pages)部分Inactive (anon) —
Inactive 链表中的匿名页(Anonymous Pages)部分Active (file) —
Active 链表中的File-backed Pages部分Inactiz V M _ } . # w `ve (file) —
Inactive 链表中的File-backed Pages部分Unevictable — 禁止换出的页,对应
Unevictable 链表,其中包括VM_LOCKED的内存页、SHM_LOCK的共享内存页(也统计在Mlocked中)、和Ramfs等Mlocked —
mlock系统调用锁定的内存大小
共享内存在 Linux 中细分的话可以分为以下几种:
SystemV Shared Memory —
shmgetPOSIX Shared Memory —
shm_openShared Anonymous Memory —
mmap(MAP_ANONYMOUS | MAP_SHARED)
共享内存在内核中都是 基于tmp0 @ i S ) ; I Wf机制实2 G 9 U # s现 的。d s * G x因为基于文件系统所以~ ] 0 w H P f就不能算是匿名页,不能计入AnonPages的统计I r V W项,而只能计入Cached和Mapp| J 1 e s / ) 8 ed统计项。但是,tmpfs背后并没有真实的磁盘文件存在,如果想要被临时释放出来,只能通过Swap的方式,所以内3 } K a Z ^存页被链接到了Inactive(anon)和Active(anon)里。
也就是说,共享内存的页面属于Fi. G / $ ~ qle-backed Pages,但是被放在Inactive(anon)和Ac$ 3 { X 6 ] : ytive(anon)链表里,统计也不算在AnonPagesA n * & u , z M里,而是算在Cached和1 y 3 OMapped里。特别地,. G / C如果, G 2 S X这些页G G z H r ! n A被m] $ | O O 4 Llock的话,就放在Unevictable链里并计算在内。所以从数值上看,z x L ~ aInactive(ak i k h ( + v s Inon)项 +Active(anon)项 不等于AnonPages项,因为前者包括共享内存的部分。Active(file)项 +Inactive(file)项 也不等于Mapped项,因为Z { t D前者中包括Unmapped的内存,后者还包含共享内存的部分(这部分在Inactive(anon)和Active(anon)里)。
这里有一个情况要注意,与文件关联的页也有可能是匿名页(MAP_PRIVATE映射的页面被修改时会产生一个匿名页拷贝),会被算到AnonPages里。
与此相关的相关的统计项有k ] ] * r ! h -:
AnonPaN # xges — 匿名页(
Anonymous pages)的大小,同时也包含Transparent HugePages (THP)对应的 AnonHugePagesMapped — 设备和文件等映射的大小,
MappedT ` I [ g 3 N统计了Cach# 8 } oed中所有的Mapped页面,是Cached的子集(满足Cached-Mapped=Unmapped)。共享内存、可执行程序的% @ / ! G , F D文件、动态库、mmap的文件等都统计在这里Shmem — 共享内存的大小,包括
Shared Memory、tmpfs和devtmpfs
注意 Linux 的内存是真正使用时才分配的,所以注意这里的大小都是已分配的大小,而不是程序里申请的大小。
下面都是内核使用的内存相关的统计项:
Slab — 内核
Slab结构使用的大小(就是那个Slab分配器占用的)SReclaimable — 内核
Slab里面可回收的部分(调用kmem_g6 n H _etpages()时带有 SLAB_RECLAIM_ACC : 5 6 L f :OUNT 标的)SUnreclaim —
Slab! m )里面无法回收的大小,等于Slab项 -SReclaimable项KernelStack — 分配给内核栈的大小(每个用户线程都会分配一个
Kernel Stack,系统调用sys# a # D Y G w Dcall、trap、exceptio} p L p v C ,n后进入内核态执行代码时候使用)P3 k aageTables — 页表的大小(就是经常挂在嘴上的那个页表)
NFS_Unstable — 发送到服务端但| = 8 3尚未提交的 NFS 页的大小
Bounce — 块设备 “bounce buffers” 部分的大小(有些老设备只能访问低端内存,比如 16M 以下,这部分分配的 buffer 统计( ) p ! $ [在这里)
WritebackTmp —S 9 5 q p FUSE 用于写回磁盘的缓冲区的大小
VmallocTotal — vmalloc 区域大D T P O w W d小
VmalloN Q F Bc: ^ a R D ( nUsed — vmalloc 区域使用大小
Vt ; d m z I fmallocChunk — vmalloc 区域最大的 free 连续区 w Y计项:
AnonHugePages — 透明大页 THP 使用的大小
HugePages_Total — 内存大页的总量,对应
/? @ Q `proc/sys/vm/nr_hugepages,可以动态改Hugei e EPages_Free — 内存大页中 free 的大小
HugePages_Rsvd — 内存大页中能分配出来的大小
HugePages_Surp —; : 2 r P 内存大页中超过
/proS b + + , F zc/sys/vm/nr_hugepages的大小, 最大值由/proc/sys/vm/nr_overcommit_hugepages限制Hugepagesize — 内存大页的页大小
进程级别的统计
先介绍几个通用U x +概念:
VSS -
Virtual Set Size,虚拟内存大小,包含共享库占用的全部内存,以及分配但未使j z _ M ! / t K 用内存RSS -
Resident Set Size,实际使用物理内存,包含了共享库占用的全部内存PSS -C c s + n
Proportional Set Size,实际使用的物理内存,共享库占用的内存按照进程数等比例划分USS -
Unique Set Size,进程独自占用的物理内存,不包含共享库占用的内存
/proc/{pid}/smaps 文件
在
/proc/{pid}/smaps文件对应每个进程的详细内存分段统计。截取一部分:下面分别解释下含义:^ U 8 . z : !
Size:映射的大小(
mapping size)Rss:实际驻留在
RAM的内存大小(包括共享库c z O - e的大小,不包括已经交换出去的页面)Pss:Rss 的基础上,把共享库的大小均摊给所有被映射的进程后的大小
Share- q $ Q @d_Ck $ d Glean:共享的
Clean内存的大小Shared_Dirty:共享的
Dirty内存的大小Private_Clean:私有的
Clean内存的大小Private_- 2 d HDirty:私有的
Dirty内存的大小Referenced:当前被标记为引用的页的大小
Anonymous:匿名内存的大小
AnonHugePages:透明大页内存的大小
Swap:
Swap的大小KernelPageSI k : t Y 9ize:内核页大小
MMUPageSize. [ G a % 3 C b:
MMU页大小Locked:被
mlock的内存大小VmFlags= c d } Y U m 2:页的标志位,有点多这里不列举,详见参考资料
可以看到7 g _ r 6 # H
Rss这个指标实际上是包含了共享库的大小的,不同的进程会共享这个映射的,如果想通过累加这个n J !值来计算所有进程用到的内存的话就不准确了,而Pss把共享库的大小均摊给了所有用到映射了这个库的进程,所以累加起来就不会重复计算共享库大小了。P.S. 最新的内核文档提到了要加
smaps_rollup这个统计,支持Pss_Anon、Pss_File和Pss_Shmem三个分类统计,这个在进程级别看,用到内存就很清晰了。我们可以累加一下这个值` S $ Q看看某进程用到的内存总和:
注意单位是KB,所以这里进程用到的内存是 1.17 GB 左右。
这是个使用共享内存作为存储的服务,所以这是符合预期的。如果想要看排除共享内存的部分,那要看
Anonymous部分的总和:所以实际匿名内存使用9 C ^ a +是 63 MB 左右。
top 命令
top命令中关于内存使用的统计:内存相关的统计有
VIRT、RES、SHR、SWAP、CODE、DATA、USEDVIRT —
Virtual Memory Size,虚拟内存大小,包括所有代码、数据和共享库,以及已交换的页面和已映射但未使用的内存RES —
Resident Memory Size,驻留内存大小,共享的内存比如动态库也会计算在内SHR —
Shared Memory Size,共享的内存大小,并非所有共享的内存都是常驻的SWAP — Swapped Size,非; $ W j & B 5驻留内存大小
CODE — Code Size,程序可执行代码的大小
DATA — Data + Sta@ @ | r P O g X ?ck Size,可执行代码以外的物理内存量,也称为数据驻留集大小
USED — Memory in Use,RES + SWAP 的大小
其他的内存查看命令
常用的还有这些:
vmstat、sar、slabtop、kmstat、ps、prstat、pma7 n ;p等等。| / o U G , R 0懒得写了,有问题看ma o U l An文档得了。参考文献
Understanding the Linux Kernel, Daniel Plerre Bovet / Marco Cesati, 2005-11
ProfessionalD } h 2 * Linux Kernel Architecture, Wolfgang Mauerer, 2008-10-13U , = y M
Systems Performance: Enterprise and the Cloud, Brendan Gregg, 201o 4 { L , 33-10-26
https://raw.githubusercontent.com/torvalds/linux/master/Documentation/filesyste u 8 T h # N pms/proc.txt
https://en.wikipedia.org/wiki/Resident5 v q H_set_size
https://en.wikipedia.org/wiki/Proportional_set_size
https://en.wikipedia.org/wiki/Unique: ? / v ] S ! b _set_size