下午公司的QA找过来,说测试环境的数据库重启后连接有问题,登录服务器我的本能反应先ps -ef | grep mysqld,是有进程的,看错误日志之前手贱的又执行一遍ps -ef,发现进程的进程启动时间变了,直觉是数据库挂了后重新被重新拉起,然后看数据库错误日志,于是就有了今天这篇笔记......

不经常写分享笔记,不知道要交待什么前提,但是我觉得没什么可磨叽的,直接看数据库error log吧,截图如下:

error-log的关键信息:

ERROR:io_setup这个函数尝试了5次都失败了,错误码是EAGAIN

Note:可以设置innodb_use_native_aio=0来禁用linux Native AIO(mysqld很讲究,在关掉自己之前还挣扎着告诉我可以怎么做)

ERROR:初始化AIO子系统失败

ERROR:innodb 插件初始化失败

有了关键信息,那么问题来了:

1、io_setup()是个什么函数?EAGAIN错误码什么意思?

2、innodb_use_native_aio这个参数是管什么的?

3、innoDB作为数据库存储引擎的插件,为什么初始化失败了?

下面我梳理了一下我的笔记,也是为了解决上面的三个问题

回答第一个问题:

io_setup(),是Linux 操作系统提供的 asynchronous I/O的接口函数

EAGAIN的错误码是aio-nr超过了aio-max-nr。

我们需要知道Linux Native Aio是由操作系统内核提供的AIO,完全非阻塞异步的,主要的几个系统调用函数为为 io_submit/io_setup/io_getevents,读写操作可以直接投递到硬件,不会浪费CPU,能够提高IO性能。

EAGAIN的错误码可以使用man来查看,或者看内核文档

$ man io_setup ...... ERRORS       EAGAIN The specified nr_events exceeds the user’s limit of available events. ...... $ less /usr/share/doc/kernel-doc-2.6.32/Documentation/sysctl/fs.txt (我截取一部分) ...... aio-nr & aio-max-nr: aio-nr is the running total of the number of events specified on the io_setup system call for all currently active aio contexts. If aio-nr reaches aio-max-nr then io_setup will fail with EAGAIN. Note that raising aio-max-nr does not result in the pre-allocation or re-sizing of any kernel data structures. ......回答第二个问题:

innodb_use_native_aio 这个参数是个开关(boolean),决定innoDB要不要使用Linux Native Aio子系统。

关于这个参数还要多唠叨点事情,不要烦,请看下去,要不然我白码这些字了。

MySQL官方文档说:

这个参数仅适用于Linux系统(其他Unix-like的系统还只能用synchronous I/O),数据库启动之前配置在my.cnf,数据库运行期间不能修改,默认是开启的可以不用特意配置,Windows 系统就有异步io的能力不需要配置(我尝试配置参数为1,能启动,但是不生效,我在网上看到一哥们windows的配置文件写这个,希望他能看到我的笔记,捂脸)。检查参数的sql:

mysql> show variables like '%innodb_use_native_aio%'; +-----------------------+-------+ | Variable_name         | Value | +-----------------------+-------+ | innodb_use_native_aio | ON   | +-----------------------+-------+ 1 row in set (0.00 sec)如果运行了大量的innoDB I/O 线程,尤其是同一台机器运行着大量这样的实例,会超过Linux系统限制,当发生超过系统限制的时候error-log记录报错码EAGAIN,官网给的解释如下:

EAGAIN: The specified maxevents exceeds the user's limit of available events.通常可以提高/proc/sys/fs/aio-max-nr限制来解决此错误。

tip:aio-max-nr修改方式如下

# vim /etc/sysctl.conf 修改或者添加fs.aio-max-nr = 65536 # sysctl -p # cat /proc/sys/fs/aio-max-nr 65536 IBM官方文档建议,通常这个值设置65536能够满足大部分的场景,下面这段话: The /proc/sys/fs/aio-nr file provides the current number of system-wide asynchronous I/O requests. The /proc/sys/fs/aio-max-nr file is the maximum number of allowable concurrent requests. The maximum is commonly 64KB, which is adequate for most applications. (详见:-async/)如果这个参数在你的配置文件中指定是开启的,那么很有可能因为异步IO子系统初始化失败而影响innoDB的启动,如果数据库启动失败,那么可以将这个参数设置为0。

这个参数也可能会被自动禁掉,当启动期间innoDB检测到潜在的问题:比如 a combination of tmpdir location, tmpfs file system, and Linux kernel that does not support AIO on tmpfs

回答第三个问题:

因为配置的innodb_use_native_aio参数是开启,异步I/O子系统失败导致innoDB的后台aio 线程初始化失败,innoDB引擎也就失败了。

那你可能要问了,innoDB引擎为什么需要aio 线程?干什么的?

回答这个问题之前我们先看粗略复习一下MySQL和innoDB的体系结构:

MySQL主要分为(Server层)和(存储引擎层)

存储引擎有那么几种,其中包括innoDB

innoDB包括(内存池)和(后台线程)

innoDB后台线程包括(IO thread),(master thread),(purge thread),(page cleanser thread)

我接下来分5个point说图中右下角的那一块io thread的内容。

point1:InnoDB 的 IO thread包括四种:(insert buffer thread)、(log thread)、(read io thread)、(write io thread)

point2:从InnoDB1.0.x版本开始read io thread和write io thread可以并行处理,并行处理的线程数通过innodb_read_io_threads和innodb_write_io_threads两个参数设置,如下sql是怎样查看版本、参数、运行时的线程:

mysql> show variables like '%innodb_v%'; +----------------+--------+ | Variable_name | Value | +----------------+--------+ | innodb_version | 5.7.18 | +----------------+--------+ 1 row in set (0.00 sec) mysql> show variables like '%io%thread%'; +-------------------------+-------+ | Variable_name           | Value | +-------------------------+-------+ | innodb_read_io_threads | 16   | | innodb_write_io_threads | 16   | +-------------------------+-------+ 2 rows in set (0.00 sec) 可以看到这些线程 mysql> show engine innodb status\G ...... -------- FILE I/O -------- I/O thread 0 state: waiting for completed aio requests (insert buffer thread) I/O thread 1 state: waiting for completed aio requests (log thread) I/O thread 2 state: waiting for completed aio requests (read thread) ...... I/O thread 16 state: waiting for completed aio requests (read thread) I/O thread 17 state: waiting for completed aio requests (read thread) I/O thread 18 state: waiting for completed aio requests (write thread) ...... I/O thread 32 state: waiting for completed aio requests (write thread) I/O thread 33 state: waiting for completed aio requests (write thread) Pending normal aio reads: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] , aio writes: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] , ibuf aio reads:, log i/o's:, sync i/o's: Pending flushes (fsync) log: 0; buffer pool: 0 33761 OS file reads, OS file writes, 19172984 OS fsyncs 0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s 可以观察到 thread id 0为 insert buffer thread 有一个线程 thread id 1为 log thread 有一个线程 read thread 比 write thread的id号小 分别为我设置的16个point3:关键点来了!!为了提高磁盘性能,在Linux系统上使用异步 I/O子系统(native AIO)来提高数据文件页的预读和脏页刷盘等写请求的性能。

point4:与Async  IO对应的是Sync IO:

SIO是进行一次IO操作请求A,需要等待A的此次操作结束才能发出B请求

AIO是发出请求A之后马上发出请求B,当全部IO请求发送出去后,等所有的IO操作完成,此外AIO还可以进行IO Merge,将多个可一起进行的请求进行合并。

point5:Native AIO需要操作系统的支持,windows和Linux都支持,但是Linux系统下,在安装编译或者运行的时候需要libaio库的支持,Linux环境开启AIO的参数配置为innodb_use_native_aio,Windows没有这个参数配置,也不用指定。

整理的相关知识点我觉得介绍差不多了,我们回归写这篇笔记的初衷:

为什么我的环境异步IO失败了,数据库启动失败?怎么解决这个问题?

还记得我的error-log的报错"EAGAIN",EAGAIN是因为aio-nr超过了aio-max-nr,我的测试环境截图如下:

MySQL官方文档告诉我们建议我们将/proc/sys/fs/aio-max-nr该值增大,我测试了一下。测试步骤如下:

(3)启动3307的实例,发现启动失败,复现问题:

(4)修改/proc/sys/fs/aio-max-nr为17610之后,成功启动。

(5)补充一下,即使aio-nr超过了aio-max-nr的情况下,已经启动的数据库不会报错,系统日志也不会报错。

那到底aio-nr的8805是什么值,怎么算的,这决定我们调大aio-max-nr的值是多少。

当我设置innodb_read_io_threads为16和innodb_write_io_threads为16的时候,aio-nr=8805=16 * 256 + 16 * 256 + 256 + 256 + 1 +100。

当我调整innodb_read_io_threads为8和innodb_write_io_threads为8的时候,aio-nr=4709=8 * 256 + 8 * 256 + 256 +256 + 1 + 100。

当我调整innodb_read_io_threads为4和innodb_write_io_threads为4的时候,aio-nr=2661=4 * 256 + 4* 256 + 256 + 256 + 1 + 100。

当我调整innodb_read_io_threads为8和innodb_write_io_threads为4的时候,aio-nr=3685=4 * 256 + 4 * 256 + 256 + 256 + 1  + 100。

然后我找了其他同事帮我写内核debug的脚本跑了一下,当我启动数据库的时候会分配aio的events给每个线程,下面是我设置innodb_read_io_threads为4和innodb_write_io_threads为4的时候分配的过程,能看到给每个线程分配的events的个数。

# stap -ve ' > global allocated, allocatedctx, freed >   > probe syscall.io_setup { >   allocatedctx[tid()] += maxevents; allocated[tid()]++; >   printf("%d AIO events requested by TID %d (%s)\n", >     maxevents, tid(), cmdline_str()); > } > probe syscall.io_destroy {freed[tid()]++} >   > probe kprocess.exit { >   if (allocated[tid()]) { >     printf("TID %d exited\n", tid()); >     delete allocated[tid()]; >     delete allocatedctx[tid()]; >     delete freed[tid()]; >   } > } >   > probe end { > foreach (tid in allocated) { >   printf("TID %d allocated=%d allocated events=%d freed=%d\n", >       tid, allocated[tid], allocatedctx[tid], freed[tid]); > } > } > ' Pass 1: parsed user script and 118 library script(s) using virt/41696res/3212shr/39172data kb, in 170usr/20sys/191real ms. Pass 2: analyzed script: 5 probe(s), 14 function(s), 97 embed(s), 3 global(s) using 244568virt/72944res/4308shr/69488data kb, in 310usr/90sys/409real ms. Pass 3: using cached /root/.systemtap/cache/bd/stap_bd255bcb211f538385aaf4d7f8b0e607_60288.c Pass 4: using cached /root/.systemtap/cache/bd/stap_bd255bcb211f538385aaf4d7f8b0e607_60288.ko Pass 5: starting run. 1 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 256 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) 100 AIO events requested by TID 35533 (/data/mysql/bin/mysqld --defaults-file=../my.cnf.3307 --basedir=/data/mysql/ --datadir=/data/mysql/data3307 --user=mysql --log-error=/data/mysql/data3307/mysql-error.log   --pid-file=/data/mysql/data3307/localhost.pid --socket=/data/mysql/data3307mysql_3307.sock --port=3307) ^CTID 35533 allocated=12 allocated events=2661 freed=0 Pass 5: run completed in 0usr/30sys/464833real ms.

我为了从坑中爬出来,也为了DBA战友们不掉到这个坑,总结一下这篇笔记,唠唠叨叨:

1、首先你如果配置innodb_use_native_aio=1,就决定你的InnoDB引擎后台线程中的IO THREAD要使用异步I/O的方式,使用了异步I/O的方式你的数据库预读和写脏页的性能会优秀一些,那么启动数据库之前你要检查数据库实例即将需要从操作系统申请多少events,公式如下:

events数=innodb_read_io_threads * 256 + innodb_write_io_threads  * 256 + log thread * 256 + insert buffer thread  * 256 + 1 +100

2、当前系统的已请求的events数 cat /proc/sys/fs/aio-nr,保证当前已请求的加上你计算的events数不能超过cat /proc/sys/fs/aio-max-nr,超过了,数据库就启动不起来了,那么拯救你的方式有如下两种。

Help1:调小你即将启动数据库的innodb_write_io_threads  和 innodb_read_io_threads  ,或者调整这台机器上其他启动mysql进程的线程数,请注意修改innodb_write_io_threads  和 innodb_read_io_threads  需要重启数据库。

Help2:调大你设置的/proc/sys/fs/aio-max-nr,满足你计算的需要总的events的数量,设置的上限我目前还没找到官方的支撑,只是看到IBM官方文档说通常这个值设置65536能够满足大部分的场景,Oracle对于这个值的设置建议是,我们生产上Mysql配置是64KB,Oracle配置3M。

3、上面两点帮助如果你做不到,比如没权限等问题,还非要启动数据库,那就开不了innodb_use_native_aio了,请老老实实使用Sync I/O。