前几天遇到一个奇怪的问题,服务器内存明明够用,结果在对MySQL进行测压的时候却出现了OOM,是Linux内核出错了吗?
具体现象如下:使用sysbench对mysql进行压测,并发50、80均正常输出,当并发达到100时开始报OOM。
[root@yang-01~]ulimit-acorefilesize(blocks,-c)0datasegsize(kbytes,-d)unlimitedschedulingpriority(-e)0filesize(blocks,-f)unlimitedpingsignals(-i)23045maxlockedmemory(kbytes,-l)64maxmemorysize(kbytes,-m)unlimitedopenfiles(-n)1024pipesize(512bytes,-p)8POSIXmessagequeues(bytes,-q)819200real-timepriority(-r)0stacksize(kbytes,-s)8192cputime(seconds,-t)unlimitedmaxuserprocesses(-u)23045virtualmemory(kbytes,-v)unlimitedfilelocks(-x)unlimited
系统并没有对ulimit进行限制,100个并发量连接在我们的配置之内,那就不是ulimit配置的问题,接下来分析下内存的使用情况,
在复现MySQLOOM的过程中,查看对应内存使用,通过top和free命令进行监控,得到以下信息,
[root@yang-02~]toptop-17:21:30up32min,4users,loadaverage:0.00,0.04,0.11Tasks:226total,2running,224sleeping,0stopped,0zombie%Cpu(s):2.2us,0.6sy,0.0ni,97.2id,0.0wa,0.0hi,0.0si,0.0stKiBMem:16432236total,9164596free,2012156used,5255484buff/cacheKiBSwap:5242876total,5242876free,0%CPU%MEMTIME+:00.27mysqld
这里看我们的内存使用很正常,free和available都很多,swap都没有使用,唯一存在异常的是虚拟内存有点高,我们接着分析:
进一步查看一下/proc/meminfo,具体分析一下内存的使用情况,其中以下两个参数引起了注意:
[root@yang-02~]cat/etc/|__memory=2
将_memory调整为0,压测时MySQLOOM消失了。
这三个参数值是什么意思呢?它和我的内存使用的关系是什么?内存真的够用吗?通过翻看Linux的内核文档我们来进行详细说明。
分析_memory的使用
首先解释下什么叫overcommit,overcommit的意思是指操作系统承诺给进程的内存大小超过了实际可用的内存。
从内核版本2.5.30开始,这个参数的三种取值分别为:
overcommit_memory:Thisvaluecontainsaflagthatenablesmemoryovercommitment.
Whenthisflagis0,Heuristicovercommithandling,thekernelattemptstoe
Whenthisflagis1,Alwaysovercommit,tmemoryconsistingalmostentirelyofzeropages.
Whenthisflagis2,Don'tovercommit,thekernelusesa"neverovercommit"p+aconfigurableamount(defaultis50%),inmostsituationsthismeansaprocesswillnotbekilledwhileaccessingpagesbutwillreceiveerrorsonmemoryallocationasappropriate.
中文释义:
当这个标志为0时,表示试探性的overcommit,当用户空间请求更多内存时,OSkernel会预估剩余的空闲内存量,如果内存申请特别大就会被拒绝。例如malloc()一次性申请的内存大小就超过了swap和physicalRAM的和,就会遭到kernel拒绝overcommit。
当这个标志为1时,kernel会假装一直有足够的内存,直到实际用完为止。
当这个标志为2时,kernel使用“永不过度提交”的策略,试图阻止任何内存的过度提交。
从含义中分析,如果我们将_memory的值设为2,就很有可能出现内存申请的值超过我们的阈值,就会受到禁止。
该阈值是通过内核参数_ratio或_kbytes间接设置的,从对应参数解释中得到公式如下:
CommitLimit=PhysicalRAM*_ratio+Swap//_ratio是内核参数,默认值是50,表示物理内存的50%。
测试环境中PhysicalRAM的值约为16G,Swap的值约为5G,计算下来可正对应CommitLimit的值13个G。
/proc/meminfo中的Committed_AS表示所有进程已经申请的内存总大小,而我们查询的free和top下的内存则是进程已经分配的内存。
Committed_AS是OSkernel对所有进程在最坏情况下需要多少RAM/swap的预估,才能保证工作负载不会出现OOM(内存不足),因此会存在过度申请提交内存的现象。
这个值是系统所有运行的程序所申请的内存大小,并不代表着分配使用的大小,而且各个程序申请的内存是可共享的。
虽然Committed_AS的数值与虚拟内存VIRT的大小很相似,目前没有找到官方说明他们之间的联系,但经过多次测试,它的大小和虚拟内存并没有关系。
结果如果Committed_AS超过CommitLimit就表示发生了overcommit,超出越多表示overcommit越严重,kernel的killer会挑一部分进程干掉,如果内存不够还会继续被killer干掉,MySQL在内存占用中属于大头,首当其冲。
测试环境查看Committed_AS和CommitLimit的参数值为:
[root@yang-02~]#cat/proc/meminfo|grepCommitCommitLimit:13458992kBCommitted_AS:13244484kB
两者已经十分接近,非常容易发生MySQL的OOM,因此需要调整的是增加内存或者将_memory的值设为0/1,具体需要根据环境变更。
附:还有两个参数与我们这次的内存分配有关系,不过影响不大,有兴趣的同学可以自行谷歌:admin_reserve_kbytes和user_reserve_kbytes。