作者:刘开洋

爱可生交付服务部团队北京 DBA,次要负责解决 MySQL 的 troubleshooting 和我司自研数据库自动化治理平台 DMP 的日常运维问题,对数据库及周边技术有浓重的学习趣味,喜爱看书,谋求技术。

本文起源:原创投稿

*爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。

问题

前几天遇到一个奇怪的问题,服务器内存明明够用,后果在对 MySQL 进行测压的时候却呈现了 OOM,是 Linux 内核出错了吗?

具体景象如下:应用 sysbench 对 mysql 进行压测,并发 50、80 均失常输入,当并发达到 100 时开始报 OOM。

[root@yang-01 ~]# sysbench /usr/share/sysbench/oltp_read_write.lua --mysql-host=yang-02 --mysql-port=3306 --mysql-user=root --mysql-password=*** --mysql-db=test --table-size=100000 --tables=5 --threads=100 --db-ps-mode=disable --auto_inc=off --report-interval=3 --max-requests=0 --time=360 --percentile=95 --skip_trx=off --mysql-ignore-errors=6002,6004,4012,2013,4016,1062 --create_secondary=off runsysbench 1.0.17 (using system LuaJIT 2.0.4) Running the test with following options:Number of threads: 100Report intermediate results every 3 second(s)Initializing random number generator from current time Initializing worker threads...  FATAL: mysql_store_result() returned error 5(Out of memory (Needed 48944 bytes))FATAL: 'thread_run' function failed: /usr/share/sysbench/oltp_common.lua:432: SQL error,errno = 5, state = 'HY000': Out of memory (Needed 48944 bytes)FATAL: mysql_store_result() returned error 5(Out of memory (Needed 48944 bytes))FATAL: 'thread_run' function failed: /usr/share/sysbench/oltp_common.lua:432: SQL error,errno = 5, state = 'HY000': Out of memory (Needed 48944 bytes)

MySQL 中 error log 的报错为:

······2021-03-16T09:13:00.692622+08:00 343 [ERROR] [MY-010934] [Server] Out of memory (Needed 708628 bytes)2021-03-16T09:13:00.692702+08:00 343 [ERROR] [MY-010934] [Server] Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use 'ulimit' to allow mysqld to use more memory or you can add more swap space2021-03-16T09:13:59.832037+08:00 374 [ERROR] [MY-000000] [InnoDB] InnoDB: Assertion failure: ut0ut.cc:678:!m_fatalInnoDB: thread 140375101384448InnoDB: We intentionally generate a memory trap.InnoDB: Submit a detailed bug report to http://bugs.mysql.com.InnoDB: If you get repeated assertion failures or crashes, evenInnoDB: immediately after the mysqld startup, there may beInnoDB: corruption in the InnoDB tablespace. Please refer toInnoDB: http://dev.mysql.com/doc/refman/8.0/en/forcing-innodb-recovery.htmlInnoDB: about forcing recovery.01:13:59 UTC - mysqld got signal 6 ;

剖析

对 MySQL OOM 咱们一步步进行剖析,首先应该查看 ulimit 限度,

[root@yang-02 ~]# ulimit -acore file size          (blocks, -c) 0data seg size           (kbytes, -d) unlimitedscheduling priority             (-e) 0file size               (blocks, -f) unlimitedpending signals                 (-i) 23045max locked memory       (kbytes, -l) 64max memory size         (kbytes, -m) unlimitedopen files                      (-n) 1024pipe size            (512 bytes, -p) 8POSIX message queues     (bytes, -q) 819200real-time priority              (-r) 0stack size              (kbytes, -s) 8192cpu time               (seconds, -t) unlimitedmax user processes              (-u) 23045virtual memory          (kbytes, -v) unlimitedfile locks                      (-x) unlimited

零碎并没有对 ulimit 进行限度,100 个并发量连贯在咱们的配置之内,那就不是 ulimit 配置的问题,接下来剖析下内存的应用状况,

在复现 MySQL OOM 的过程中,查看对应内存应用,通过 top 和 free 命令进行监控,失去以下信息,

[root@yang-02 ~]# free -m              total        used        free      shared  buff/cache   availableMem:          16047        1958        8956         8        5132       13920Swap:          5119           0        5119  [root@yang-02 ~]# toptop - 17:21:30 up 32 min,  4 users,  load average: 0.00, 0.04, 0.11Tasks: 226 total,   2 running, 224 sleeping,   0 stopped,   0 zombie%Cpu(s):  2.2 us,  0.6 sy,  0.0 ni, 97.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 stKiB Mem :  16432236 total,  9164596 free,  2012156 used,   5255484 buff/cacheKiB Swap:        5242876 total,    5242876 free,        0 used.  14247508 avail Mem   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND 7654 root      20   0   13.7g   1.7g  14312 S   1.3 10.3   0:00.27 mysqld

这里看咱们的内存应用很失常,free 和 available 都很多,swap 都没有应用,惟一存在异样的是虚拟内存有点高,咱们接着剖析:

进一步查看一下 /proc/meminfo,具体分析一下内存的应用状况,其中以下两个参数引起了留神:

[root@yang-02 ~]# cat /proc/meminfo | grep CommitCommitLimit:     13458992 kBCommitted_AS:    13244484 kB

一般来说 CommitLimit 的值是要比 Committed_AS 的值要小的,联合当初内存的应用,咱们应该留神到一个 OS kernel 参数

[root@yang-02 ~]# cat /etc/sysctl.conf |grep vm.overcommit_memoryvm.overcommit_memory=2

将 vm.overcommit_memory 调整为0,压测时 MySQL OOM 隐没了。

这三个参数值是什么意思呢?它和我的内存应用的关系是什么?内存真的够用吗?通过翻看 Linux 的内核文档咱们来进行具体阐明。

剖析 vm.overcommit_memory 的应用

首先解释下什么叫 overcommit,overcommit 的意思是指操作系统承诺给过程的内存大小超过了理论可用的内存。

从内核版本 2.5.30 开始,这个参数的三种取值别离为:

overcommit_memory:This value contains a flag that enables memory overcommitment.

  • When this flag is 0, Heuristic overcommit handling, the kernel attempts to estimate the amount of free memory left when userspace requests more memory. Obvious overcommits of address space are refused. Used for a typical system. It ensures a seriously wild allocation fails while allowing overcommit to reduce swap usage. root is allowed to allocate slightly more memory in this mode. This is the default.
  • When this flag is 1, Always overcommit, the kernel pretends there is always enough memory until it actually runs out. Appropriate for some scientific applications. Classic example is code using sparse arrays and just relying on the virtual memory consisting almost entirely of zero pages.
  • When this flag is 2, Don't overcommit, the kernel uses a "never overcommit" policy that attempts to prevent any overcommit of memory. The total address space commit for the system is not permitted to exceed swap + a configurable amount (default is 50%) of physical RAM. Depending on the amount you use, in most situations this means a process will not be killed while accessing pages but will receive errors on memory allocation as appropriate.

中文释义:

  • 当这个标记为 0 时,示意试探性的 overcommit,当用户空间申请更多内存时,OS kernel 会预估残余的闲暇内存量,如果内存申请特地大就会被回绝。例如 malloc() 一次性申请的内存大小就超过了 swap 和 physical RAM 的和,就会受到 kernel 回绝overcommit。
  • 当这个标记为 1 时,kernel 会伪装始终有足够的内存,直到理论用完为止。
  • 当这个标记为 2 时,kernel 应用“永不适度提交”的策略,试图阻止任何内存的适度提交。

从含意中剖析,如果咱们将 vm.overcommit_memory 的值设为 2,就很有可能呈现内存申请的值超过咱们的阈值,就会受到禁止。

该阈值是通过内核参数 vm.overcommit_ratio 或 vm.overcommit_kbytes 间接设置的,从对应参数解释中失去公式如下:

CommitLimit = Physical RAM * vm.overcommit_ratio + Swap                  // vm.overcommit_ratio 是内核参数,默认值是 50,示意物理内存的 50%。

测试环境中 Physical RAM 的值约为 16G,Swap 的值约为 5G,计算下来可正对应 CommitLimit 的值 13 个 G。

/proc/meminfo 中的 Committed_AS 示意所有过程曾经申请的内存总大小,而咱们查问的 free 和 top 下的内存则是过程曾经调配的内存。

Committed_AS 是 OS kernel 对所有过程在最坏状况下须要多少 RAM/swap 的预估,能力保障工作负载不会呈现 OOM(内存不足),因而会存在适度申请提交内存的景象。

这个值是零碎所有运行的程序所申请的内存大小,并不代表着调配应用的大小,而且各个程序申请的内存是可共享的。

尽管 Committed_AS 的数值与虚拟内存 VIRT 的大小很类似,目前没有找到官网阐明他们之间的分割,但通过屡次测试,它的大小和虚拟内存并没有关系。

后果

如果 Committed_AS 超过 CommitLimit 就示意产生了overcommit,超出越多示意 overcommit 越重大,kernel 的 killer 会挑一部分过程干掉,如果内存不够还会持续被 killer 干掉,MySQL 在内存占用中属于大头,首当其冲。

测试环境查看 Committed_AS 和 CommitLimit 的参数值为:

[root@yang-02 ~]# cat /proc/meminfo | grep CommitCommitLimit:     13458992 kBCommitted_AS:    13244484 kB

两者曾经非常靠近,非常容易产生 MySQL 的 OOM,因而须要调整的是减少内存或者将 vm.overcommit_memory 的值设为 0/1,具体须要依据环境变更。

附:还有两个参数与咱们这次的内存调配有关系,不过影响不大,有趣味的同学能够自行谷歌:admin_reserve_kbytes 和 user_reserve_kbytes。

参考:

https://www.kernel.org/doc/Do...

https://www.kernel.org/doc/Do...

http://lwn.net/Articles/28345/

https://www.win.tue.nl/~aeb/l...