月度归档:2016年11月

找到MySQL服务器发生SWAP罪魁祸首

0、导读

MySQL数据库服务器发生SWAP相信很多人都遇到过,如何找出元凶,又如何应对呢?

1、写在前面

在昨晚的知数堂公开课中,其实用的就是本次的案例。本次公开课的PPT、视频已上传到百度云盘,链接: https://pan.baidu.com/s/1eR53Qd8,欢迎转存及转发(扫描下方二维码识别)

知数堂培训全新MySQL DBA课程第九期和Python运维开发班第三期正在招生中,扫描下方二维码加入QQ群 529671799 (加群暗号:知数堂) 获得最新信息。

关于知数堂

http://zhishuedu.com

“知数堂培训”是由资深MySQL专家叶金荣、吴炳锡联合推出专业优质在线培训课程,主要有MySQL DBA实战优化和Python运维开发两个课程,是业内最有良心、最有品质的培训课程。

2、问题交代

我的朋友小芳(这次不是小明了,hoho),最近遇到了一个郁闷的问题:明明OS还有大量的空闲内存,可是却发生了SWAP,百思不得其解,她就来找我搬救兵了(嗯,我可不是🐒派来的)。

先看下SWAP是干嘛的,了解下它的背景知识。

在Linux下,SWAP的作用类似Windows系统下的“虚拟内存”。当物理内存不足时,拿出部分硬盘空间当SWAP分区(虚拟成内存)使用,从而解决内存容量不足的情况。

SWAP意思是交换,顾名思义,当某进程向OS请求内存发现不足时,OS会把内存中暂时不用的数据交换出去,放在SWAP分区中,这个过程称为SWAP OUT当某进程又需要这些数据且OS发现还有空闲物理内存时,又会把SWAP分区中的数据交换回物理内存中,这个过程称为SWAP IN。在vmstat的输出结果中,分别表现为 si\so 两列,如下图.1

swap1图1

看到这里我们就知道了,发生SWAP的最直接可能的原因是进程向OS申请内存时,发现物理内存不足,当没有SWAP可用的话,这时可能会一直等待,也可能会触发OOM-killer机制,OS把消耗内存最多的那个进程kill掉以释放内存,这个选择取决于内核参数 vm.swappiness。该参数可选范围从 0 – 100,设为 0 就是希望最大限度使用物理内存,尽量不使用swap,设为 100 则是希望积极使用swap。在运行数据库进程的服务器上,我们通常强烈建议这个值小于等于10,最好是设置为 0。原因很简单,对数据库这种需要集中CPU资源、大内存、高I/O的程序而言,如果用SWAP分区代替内存,那数据库服务性能将是不可接受的,还不如直接被OOM kill(数据库进程通常占用最多内存,最容易被OOM kill)来的痛快(早死晚死都是死,还不如痛快的死,反正很快就能重生,嗯)。

先介绍完这么多信息,大家肯定已经不耐烦了,我们就来看看现场并进行排查吧。

3、现场排查

首先,看下系统整体的状况,如下图.2所示
swap3图2

从上图能看出来什么呢,有几个关键信息:

  • 系统负载不算高,最近的平均load是6.8;
  • CPU负载也不算高,有大量的空闲,idle为 98.4%;
  • 内存主要分配给mysqld进程,占用了80.2%;
  • 尽管物理内存有256G,空闲的也将近39G,但确实发生swap了,并且把SWAP都耗尽了。

得到第一个排查结果:物理内存还有不少空闲,但却把swap都耗尽了。作为一个有经验的DBA,遇到这种情况第一反应是什么呢?嗯,先不点破,继续往下看。

再执行 free -gt 查看内存、SWAP消耗情况,如下图.3所示swap4-jpg
图3

看出来了吧,尤其是参加过 知数堂培训MySQL DBA优化班课程 的同学应该都知道,我们在课上多次强调:遇到这种情况,第一条件反射很直接就是:发生内存泄露(memory leak)了。

一般来说,如果发现内存统计结果中,cached 和 used 相差特别大的话,基本可确定系统发生内存泄露。相应的处理手法有:

  • 治标的办法:择机重启进程,彻底释放内存归还给OS;
  • 治本的办法:找到代码中导致泄露的代码,修复之(我们这次面对的是mysql代码,还是去官方提交bug吧,哈哈);
  • 治本的办法:升级程序版本,通常新版本会解决旧版本存在的问题,推荐此方案。

再看下MySQL中内存相关选项怎么配置的:
swap8-jpg
图4

除了 innodb-buffer-pool 分配的稍微多一些外,其他的还算正常。看了下,MySQL的版本是 5.6.19,看来是有必要升级到5.6系列的最新版本。

到这里,我们得到第二个排查结果:mysqld进程发生内存泄露,建议择机重启进程,并尽快安排升级到最新版本

然而,仅仅是因为mysqld进程内存泄露导致的SWAP吗,貌似不全然?还记得上面我们有个地方还没点破的不:物理内存还有不少空闲,但把swap都耗尽了。同样滴,这种案例在我们知数堂的MySQL DBA培训课程里也被多次谈及,绝大多数情况是因为没有关闭NUMA引起的。在运行数据库进程的服务器上,强烈建议关闭NUMA,在之前的分享 比较全面的MySQL优化参考(上篇) 中也有提及。我们接着来看下NUMA的状况:

swap5-jpg
图5

swap6-jpg
图6

从上面图.5、图.6可见,NUMA问题导致其中一个CPU可分配的内存远小于另一个(1.8G vs 38G),那么这个CPU上如果要申请大内存时,显然不够了,所以发生SWAP。关于NUMA的相关背景知识我这里不赘述。

因此,我们得到第三个排查结果:由于服务器硬件、系统设置不当,没有关闭NUMA,导致发生SWAP。建议方案有:

  • 在BIOS设置层面关闭NUMA,缺点是需要重启OS;
  • 或修改GRUB配置文件,缺点也是要重启OS;
  • 升级MySQL版本到5.6.27及以后,新增了一个选项 innodb_numa_interleave,只需要重启mysqld实例,无需重启OS,推荐此方案。

说到这里,这个问题已经基本分析清楚了,相关的解决建议也给了,根据自己的情况去评估选择哪个方案即可。

4、写在最后

类似的案例发生也不是一次两次了,我肯定以后还会继续存在,看完案例的的同学也没办法立刻把所有服务器上的NUMA策略全部修整过来,或者可能冲动一下想修复,但过几天就又给忘光了。

老叶曾多次在各种场合不厌其烦地强调一些基础的MySQL运维、开发规范,也正是因为看到了其实还有不少此类问题的存在。这些基础的规范都没执行到位的话,早晚是有一天要去填坑的,不管是填自己的挖下的坑,还是前人留下的坑,哈哈。

最后,作为一篇有诚意的案例总结,需要有诚意的广告才能搭配。

老叶茶馆镇店之宝,扫码识别或访问 http://yejinrong.com 直达

FAQ系列 | 是什么导致MySQL数据库服务器磁盘I/O高?

0、导读

有个MySQL服务器的磁盘I/O总有过高报警,怎么回事?

1、问题

我的朋友小明,TA有个MySQL服务器最近总是报告磁盘I/O非常高,想着我这有免费的不用白不用的企业技术服务(TA自己这么想的),就找我帮忙给把把脉。

作为一个经验丰富(踩坑不断)的DBA,出现这种问题,一般来说,磁盘I/O很高无非是下面几个原因引起:

  1. 磁盘子系统设备性能差,或采用ext2/ext3之类文件系统,或采用cfq之类的io scheduler,所以IOPS提上不去;
  2. SQL效率不高,比如没有索引,或者一次性读取大量数据,所以需要更多的I/O;
  3. 可用内存太小,内存中能缓存/缓冲的数据不多,所以需要更多的I/O。

方法论已有,接下来就是动手开始排查了。

2、排查

先看磁盘I/O设备,是由十几块SSD组成的RAID 10阵列,按理说I/O性能应该不至于太差,看iops和%util的数据也确实如此。

innodb-anli10

再来看下文件系统、io scheduler的因素,发现采用xfs文件系统,而且io scheduler用的是noop,看来也不是这个原因。而且看了下iostat的数据,发现iops也不算低,说明I/O能力还是可以的。

innodb-anli2

再来看看当前的processlist,以及slow query log,也没发现当前有特别明显的slow query,所以也不是这个原因了。

innodb-anli8

现在只剩下内存不足这个因素了,看了下服务器物理内存是64G,用系统命令 free 看了下,发现大部分都在cached,而free的也不多。观察InnoDB相关的配置以及status,看能不能找到端倪。

首先,看下 innodb-buffer-pool-size 分配了多少:

innodb-anli9-jpg

嗯,分配了18G,好像不是太多啊~

再看一下 innodb status:

innodb-anli4-jpg

重点关注下几个wait值,再看下show engine innodb结果:

innodb-anli5-jpg

关注下unpurge列表大小,看起来还是比较大的(有111万)。

更为诡异的是,在已经停掉SLAVE IO & SQL线程后,发现redo log还在一直增长…

第一次看

innodb-anli6-jpg

停掉SLAVE线程后过阵子再看

innodb-anli7-jpg

看到这里,有经验的DBA应该基本上能想明白了,主要是因为 innodb buffer pool 太小,导致了下面几个后果:

  1. dirty page 和 data page 之间相互“排挤抢占”,所以会出现 Innodb_buffer_pool_wait_free 事件;
  2. redo log 也没办法及时刷新到磁盘中,所以在SLAVE线程停掉后,能看到LSN还在持续增长;
  3. 同时我们也看到unpurge的列表也积攒到很大(111万),这导致了ibdata1文件涨到了146G之大,不过这个可能也是因为有某些事务长时间未提交。

还有,不知道大家注意到没,Innodb_row_lock_current_waits 的值竟然是 18446744073709551615(想想bigint多大),显然不可能啊。事实上,这种情况已经碰到过几次了,明明当前没有行锁,这个 status 值却不小,查了一下官方bug库,竟然只报告了一例,bug id是#71520。

3、解决

既然知道原因,问题解决起来也就快了,我们主要做了下面几个调整:

  • 调大innodb-buffer-pool-size,原则上不超过物理内存的70%,所以设置为40G;
  • 调大innodb-purge-thread,原来是1,调整成4;
  • 调大innodb_io_capacity和innodb_io_capacity_max,值分别为2万和2.5万;

调整完后,重启实例(5.7版本前调整innodb-buffer-pool-size 和 innodb-purge-thread 需要重启才生效)。再经观察,发现IOPS下降的很快,不再告警,同时 Innodb_buffer_pool_wait_free 也一直为 0,unpurge列表降到了数千级别,搞定,收工,继续搬砖卖茶~

老叶茶馆镇店之宝,访问 http://yejinrong.com 直达