钉钉群收到告警,redis服务两个挂了。登录服务器通过dmesg命令查看进程挂掉原因,看到以下日志
[21367253.038320] Node 0 DMA free:15908kB min:8kB low:8kB high:12kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15992kB managed:15908kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes
[21367253.046752] lowmem_reserve[]: 0 2825 96503 96503
[21367253.048506] Node 0 DMA32 free:376572kB min:1976kB low:2468kB high:2964kB active_anon:2441228kB inactive_anon:16kB active_file:12kB inactive_file:16kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:3129212kB managed:2893660kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:16kB slab_reclaimable:49568kB slab_unreclaimable:2944kB kernel_stack:448kB pagetables:16104kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:8 all_unreclaimable? no
[21367253.057509] lowmem_reserve[]: 0 0 93677 93677
[21367253.059225] Node 0 Normal free:85808kB min:65592kB low:81988kB high:98388kB active_anon:94857072kB inactive_anon:256kB active_file:3492kB inactive_file:3120kB unevictable:0kB isolated(anon):0kB isolated(file):128kB present:97517568kB managed:95925820kB mlocked:0kB dirty:0kB writeback:0kB mapped:996kB shmem:620kB slab_reclaimable:72616kB slab_unreclaimable:35820kB kernel_stack:4752kB pagetables:227860kB unstable:0kB bounce:0kB free_pcp:2560kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:9970 all_unreclaimable? yes
[21367253.069788] lowmem_reserve[]: 0 0 0 0
[21367253.071491] Node 0 DMA: 1*4kB (U) 0*8kB 0*16kB 1*32kB (U) 2*64kB (U) 1*128kB (U) 1*256kB (U) 0*512kB 1*1024kB (U) 1*2048kB (M) 3*4096kB (M) = 15908kB
[21367253.075622] Node 0 DMA32: 2355*4kB (UEM) 2103*8kB (UEM) 584*16kB (UEM) 293*32kB (UEM) 131*64kB (UEM) 5*128kB (EM) 58*256kB (UM) 275*512kB (UM) 163*1024kB (M) 0*2048kB 0*4096kB = 376548kB
[21367253.080198] Node 0 Normal: 21012*4kB (UEM) 251*8kB (UEM) 21*16kB (EM) 2*32kB (EM) 1*64kB (M) 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 86520kB
[21367253.084547] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=1048576kB
[21367253.086812] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB
[21367253.089058] 2129 total pagecache pages
[21367253.090883] 0 pages in swap cache
[21367253.092648] Swap cache stats: add 0, delete 0, find 0/0
[21367253.094588] Free swap = 0kB
[21367253.096303] Total swap = 0kB
[21367253.098010] 25165693 pages RAM
[21367253.099735] 0 pages HighMem/MovableOnly
[21367253.101522] 456846 pages reserved
[21367253.103259] [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
[21367253.105522] [ 3086] 0 3086 20024 87 45 0 0 systemd-journal
[21367253.107824] [ 5969] 0 5969 6718 217 18 0 0 systemd-logind
[21367253.110105] [ 5970] 81 5970 14564 179 31 0 -900 dbus-daemon
[21367253.112372] [ 5983] 0 5983 615231 10802 91 0 0 CmsGoAgent.linu
[21367253.114683] [ 6054] 0 6054 27523 33 11 0 0 agetty
[21367253.116926] [ 6058] 0 6058 27523 33 11 0 0 agetty
[21367253.119135] [ 6316] 0 6316 26839 498 51 0 0 dhclient
[21367253.121366] [ 7035] 0 7035 138962 1919 39 0 0 ops-updater
[21367253.123756] [ 7079] 0 7079 589598 6289 85 0 0 falcon-agent
[21367253.126011] [13594] 1000 13594 11667269 6644047 19394 0 0 redis-server
[21367253.128270] [13599] 1000 13599 8243013 6664731 15518 0 0 redis-server
[21367253.130502] [15685] 1000 15685 6917957 5449843 13398 0 0 redis-server
[21367253.132724] [15693] 1000 15693 5857093 5411665 11080 0 0 redis-server
[21367253.134943] [32218] 1000 32218 127813 52828 152 0 0 redis-server
[21367253.137165] [32223] 1000 32223 113477 56705 139 0 0 redis-server
[21367253.139349] [32736] 1000 32736 28295 67 13 0 0 sh
[21367253.141429] [20078] 0 20078 78343 3993 104 0 0 salt-minion
[21367253.143574] [20083] 0 20083 237123 11662 163 0 0 salt-minion
[21367253.145663] [20087] 0 20087 103223 6598 113 0 0 salt-minion
[21367253.147739] [14046] 0 14046 10494 216 13 0 0 aliyun-service
[21367253.149802] [ 3366] 0 3366 109273 676 28 0 0 AliHips
[21367253.151773] [ 9827] 0 9827 6614 359 18 0 0 AliYunDunUpdate
[21367253.153774] [ 9944] 0 9944 32536 1953 59 0 0 AliYunDun
[21367253.155691] [ 340] 0 340 28230 258 57 0 -1000 sshd
[21367253.157612] [ 483] 28 483 148240 135 43 0 0 nscd
[21367253.159441] [ 557] 0 557 13882 112 26 0 -1000 auditd
[21367253.161233] [ 691] 998 691 24120 95 18 0 0 chronyd
[21367253.163013] [ 729] 0 729 31573 157 18 0 0 crond
[21367253.164794] [ 814] 999 814 153061 2128 63 0 0 polkitd
[21367253.166505] [28544] 0 28544 11152 105 23 0 -1000 systemd-udevd
[21367253.168272] [28629] 0 28629 160279 185 142 0 0 rsyslogd
[21367253.169980] Out of memory: Kill process 13599 (redis-server) score 270 or sacrifice child
[21367253.171663] Killed process 13599 (redis-server) total-vm:32972052kB, anon-rss:26658828kB, file-rss:88kB, shmem-rss:0kB
日志内容显示 Out of memory: Kill process 13599 (redis-server)
,所以是由于内存不足,被系统给结束了进程。
原因分析
1). redis-server进程触发了oom killer,既redis要申请的内存大于了系统可用的物理内存大小。/proc/sys/vm/min_free_kbytes 参数来控制,当系统可用内存(不包含buffer和cache)小于这个值的时候,系统会启动内核线程kswapd来对内存进行回收。而还是触发了oom killer,则表明内存真的不够用了或者在内存回收前或者回收中直接触发了oom killer。
2). 如下的输出表明了申请了3次内存都没有成功
[21367253.038320] Node 0 DMA free:15908kB min:8kB low:8kB high:12kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15992kB managed:15908kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes
[21367253.046752] lowmem_reserve[]: 0 2825 96503 96503
[21367253.048506] Node 0 DMA32 free:376572kB min:1976kB low:2468kB high:2964kB active_anon:2441228kB inactive_anon:16kB active_file:12kB inactive_file:16kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:3129212kB managed:2893660kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:16kB slab_reclaimable:49568kB slab_unreclaimable:2944kB kernel_stack:448kB pagetables:16104kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:8 all_unreclaimable? no
[21367253.057509] lowmem_reserve[]: 0 0 93677 93677
[21367253.059225] Node 0 Normal free:85808kB min:65592kB low:81988kB high:98388kB active_anon:94857072kB inactive_anon:256kB active_file:3492kB inactive_file:3120kB unevictable:0kB isolated(anon):0kB isolated(file):128kB present:97517568kB managed:95925820kB mlocked:0kB dirty:0kB writeback:0kB mapped:996kB shmem:620kB slab_reclaimable:72616kB slab_unreclaimable:35820kB kernel_stack:4752kB pagetables:227860kB unstable:0kB bounce:0kB free_pcp:2560kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:9970 all_unreclaimable? yes
[21367253.069788] lowmem_reserve[]: 0 0 0 0
[21367253.071491] Node 0 DMA: 1*4kB (U) 0*8kB 0*16kB 1*32kB (U) 2*64kB (U) 1*128kB (U) 1*256kB (U) 0*512kB 1*1024kB (U) 1*2048kB (M) 3*4096kB (M) = 15908kB
[21367253.075622] Node 0 DMA32: 2355*4kB (UEM) 2103*8kB (UEM) 584*16kB (UEM) 293*32kB (UEM) 131*64kB (UEM) 5*128kB (EM) 58*256kB (UM) 275*512kB (UM) 163*1024kB (M) 0*2048kB 0*4096kB = 376548kB
[21367253.080198] Node 0 Normal: 21012*4kB (UEM) 251*8kB (UEM) 21*16kB (EM) 2*32kB (EM) 1*64kB (M) 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 86520kB
[21367253.084547] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=1048576kB
[21367253.086812] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB
[21367253.089058] 2129 total pagecache pages
[21367253.090883] 0 pages in swap cache
[21367253.092648] Swap cache stats: add 0, delete 0, find 0/0
[21367253.094588] Free swap = 0kB
[21367253.096303] Total swap = 0kB
[21367253.098010] 25165693 pages RAM
[21367253.099735] 0 pages HighMem/MovableOnly
[21367253.101522] 456846 pages reserved
各个zone的情况如何?
[21367253.038320] Node 0 DMA free:15908kB min:8kB low:8kB high:12kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15992kB managed:15908kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes
lowmem_reserve[]: 0 2825 96503 96503
[21367253.048506] Node 0 DMA32 free:376572kB min:1976kB low:2468kB high:2964kB active_anon:2441228kB inactive_anon:16kB active_file:12kB inactive_file:16kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:3129212kB managed:2893660kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:16kB slab_reclaimable:49568kB slab_unreclaimable:2944kB kernel_stack:448kB pagetables:16104kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:8 all_unreclaimable? no
lowmem_reserve[]: 0 0 93677 93677
[21367253.059225] Node 0 Normal free:85808kB min:65592kB low:81988kB high:98388kB active_anon:94857072kB inactive_anon:256kB active_file:3492kB inactive_file:3120kB unevictable:0kB isolated(anon):0kB isolated(file):128kB present:97517568kB managed:95925820kB mlocked:0kB dirty:0kB writeback:0kB mapped:996kB shmem:620kB slab_reclaimable:72616kB slab_unreclaimable:35820kB kernel_stack:4752kB pagetables:227860kB unstable:0kB bounce:0kB free_pcp:2560kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:9970 all_unreclaimable? yes
lowmem_reserve[]: 0 0 0 0
可以看到Normal还有85808KB,DMA32还有376572 KB,DMA还有15908KB,
各个zone的free都大于min,分配是有链条的,Normal不够了,会从DMA32以及DMA去请求分配,所以为什么分配失败了呢?
虽然说分配内存会按照Normal、DMA32、DMA的顺序去分配,但是低端内存相对来说更宝贵些,为了防止低端内存被高端内存用完,,linux设计了保护机制,也就是lowmen_reserve,从上面的日志看,他们的值是这样的:
- DMA(index=0): lowmem_reserve[]:0 2825 96503 96503
- DMA32(index=1)owmem_reserve[]: 0 0 93677 93677
- DMA32(index=2)lowmem_reserve[]: 0 0 0 0
lowmen_reserve的值是一个数组,当Normal(index=2)像DMA32申请内存的时候,需要满足条件:
DMA32 high+lowmem_reserve[2] < free,才能申请,来算下:
Normal:从自己这里申请,free(85808kB) > min(65592kB ),可以申请,但是只能申请 85808kB-65592kB = 20216kb,不够申请的内存,所以申请失败了(watermark[min]以下的内存属于系统的自留内存,用以满足特殊使用,所以不会给用户态的普通申请来用)
Normal转到DMA32申请:high(2964kB) + lowmem_reserve2*4 > DMA32 Free(376572kB),不允许申请
Normal转到DMA申请:high(12kB) + lowmem_reserve2*4 > DMA Free(15908kB),不允许申请,所以….最终失败了
min_free_kbytes
这里属于扩展知识了,和分析oom问题不大。我们知道了每个区都有min、low、high,那他们是怎么计算出来的,就是根据min_free_kbytes计算出来的,他本身在系统初始化的时候计算,最小128K,最大64M
- watermark[min] = min_free_kbytes换算为page单位即可,假设为min_free_pages。(因为是每个zone各有一套watermark参数,实际计算效果是根据各个zone大小所占内存总大小的比例,而算出来的per zone min_free_pages)
- watermark[low] = watermark[min] * 5 / 4
- watermark[high] = watermark[min] * 3 / 2
min 和 low的区别
- min下的内存是保留给内核使用的;当到达min,会触发内存的direct reclaim
- low水位比min高一些,当内存可用量小于low的时候,会触发 kswapd回收内存,当kswapd慢慢的将内存 回收到high水位,就开始继续睡眠
3)被干掉进程信息
[21367253.169980] Out of memory: Kill process 13599 (redis-server) score 270 or sacrifice child
[21367253.171663] Killed process 13599 (redis-server) total-vm:32972052kB, anon-rss:26658828kB, file-rss:88kB, shmem-rss:0kB
被kill的进程pid是13599.进程所占用的内存页是 6664731,换算内存大概占用 换算为内存占用量是6664731*4096 = 26GB和 anon-rss:26658828kB显示占用的内存差不多。
[21367253.103259] [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
[21367253.105522] [ 3086] 0 3086 20024 87 45 0 0 systemd-journal
[21367253.107824] [ 5969] 0 5969 6718 217 18 0 0 systemd-logind
[21367253.110105] [ 5970] 81 5970 14564 179 31 0 -900 dbus-daemon
[21367253.112372] [ 5983] 0 5983 615231
进程输出的含义是:
- pid进程ID。
- uid用户ID。
- tgid线程组ID。
- total_vm虚拟内存使用(单位为4 kB内存页)
- rss 常驻内存使用(单位4 kB内存页)
- nr_ptes页表项
- swapents交换条目
- oom_score_adj通常为0;较低的数字表示当调用OOM杀手时,进程将不太可能死亡。 值越高越被系统有限结束进程。
(4)解决办法
这个redis进程占用了近26G的内存,而redis配置文件最大配置的内存是20G,也就是除了20g数据占用的内存,程序进程本身占用了近6G的数据。我们服务器可用内存是98G,之所以这次内存会不够用的原因是这台服务器部署了5个redis节点,每个节点的最大可用内存设置都是20G,这次由于大量写入数据导致redis节点内存暴增。所以解决办法就是减少部署的节点,比如机器总共内存是98G,最多部署4个节点,还有就是增减内存占用阈值报警监控,另外就是大量数据写入前检查机器节点内存是否够用。
扩展
1.什么是oom killer
他是Linux内核设计的一种机制,在内存不足的会后,选择一个占用内存较大的进程并kill掉这个进程,以满足内存申请的需求(内存不足的时候该怎么办,其实是个两难的事情,oom killer算是提供了一种方案吧)
2.在什么时候触发?
在内存不足的时候触发,再往细节看,主要牵涉到【linux的物理内存结构】和【overcommit机制】
2.1 内存结构 node、zone、page、order
对于物理内存内存,linux会把它分很多区(zone),zone上还有node的概念,zone下面是很多page,这些page是根据buddy分配算法组织的
上面的概念做下简单的介绍,对后面分析oom killer日志很有必要:
Node:每个CPU下的本地内存节点就是一个Node,如果是UMA架构下,就只有一个Node0,在NUMA架构下,会有多个Node
Zone:每个Node会划分很多域Zone,大概有下面这些
- ZONE_DMA:定义适合DMA的内存域,该区域的长度依赖于处理器类型。比如ARM所有地址都可以进行DMA,所以该值可以很大,,或者干脆不定义DMA类型的内存域。而在IA-32的处理器上,一般定义为16M。
- ZONE_DMA32:只在64位系统上有效,为一些32位外设DMA时分配内存。如果物理内存大于4G,该值为4G,否则与实际的物理内存大小相同。
- ZONE_NORMAL:定义可直接映射到内核空间的普通内存域。在64位系统上,如果物理内存小于4G,该内存域为空。而在32位系统上,该值最大为896M。
- ZONE_HIGHMEM:只在32位系统上有效,标记超过896M范围的内存。在64位系统上,由于地址空间巨大,超过4G的内存都分布在ZONE_NORMA内存域。
- ZONE_MOVABLE:伪内存域,为了实现减小内存碎片的机制。
分配价值链
- 除了只能在某个区域分配的内存(比如ZONE_DMA),普通的内存分配会有一个“价值”的层次结构,按分配的“廉价度”依次为:ZONE_HIGHMEM > ZONE_NORMAL > ZONE_DMA。
- 即内核在进行内存分配时,优先从高端内存进行分配,其次是普通内存,最后才是DMA内存
Page zone下面就是真正的内存页了,每个页基础大小是4K,他们维护在一个叫free_area的数组结构中。
- order:数组的index,也叫order,实际对应的是page的大小,比如order为0,那么就是一堆1个空闲页(4K)组成的链表,order为1,就是一堆2个空闲页(8K)组成的链表,order为2,就是一堆4个空闲页(16K)组成的链表
2.2 overcommit
根据2.1,已经知道物理内存的大概结构以及分配的规则,不过实际上还有虚拟内存的存在,他的overcommit机制和oom killer有很大关系:
在实际申请内存的时候,比如申请1G,并不会在物理区域中分配1G的真实物理内存,而是分配1G的虚拟内存,等到需要的时候才去真正申请物理内存,也就是说申请不等于分配
所以说,可以申请比物理内存实际大的内存,也就是overcommit,这样会面临一个问题,就是当真的需要这么多内存的时候怎么办—>oom killer!
vm.overcommit_memory 接受三种值:
- 0 – Heuristic overcommit handling. 这是缺省值,它允许overcommit,但过于明目张胆的overcommit会被拒绝,比如malloc一次性申请的内存大小就超过了系统总内存
- 1 – Always overcommit. 允许overcommit,对内存申请来者不拒。
- 2 – Don’t overcommit. 禁止overcommit。
3.oom killer 怎么挑选进程?
linux会为每个进程算一个分数,最终他会将分数最高的进程kill
/proc/
- 在计算最终的 badness score 时,会在计算结果是中加上 oom_score_adj,取值范围为-1000到1000
- 如果将该值设置为-1000,则进程永远不会被杀死,因为此时 badness score 永远返回0。
/proc/
- 取值是-17到+15,取值越高,越容易被干掉。如果是-17,则表示不能被kill
- 该设置参数的存在是为了和旧版本的内核兼容
/proc/
- 这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom_adj计算出的,消耗内存越多分越高,存活时间越长分越低
子进程内存:
Linux在计算进程的内存消耗的时候,会将子进程所耗内存的一半同时算到父进程中。这样,那些子进程比较多的进程就要小心了。
关闭 OOM killer
- sysctl -w vm.overcommit_memory=2
- echo “vm.overcommit_memory=2” >> /etc/sysctl.conf
3.1 找出最优可能被杀掉的进程
vi oomscore.sh
#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
printf "%2d %5d %s\n" \
"$(cat $proc/oom_score)" \
"$(basename $proc)" \
"$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10
chmod +x oomscore.sh
./oomscore.sh
3.2 避免的oom killer的方案
- 直接修改/proc/PID/oom_adj文件,将其置位-17
- 修改/proc/sys/vm/lowmem_reserve_ratio
- 直接关闭oom-killer
参考:
http://bhsc881114.github.io/2018/06/24/oom-killer%E7%90%86%E8%A7%A3%E5%92%8C%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90-%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90/
http://bhsc881114.github.io/2018/06/24/oom%20killer%E7%90%86%E8%A7%A3%E5%92%8C%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90:%E7%9F%A5%E8%AF%86%E5%82%A8%E5%A4%87/
https://blog.51cto.com/leejia/1952482