MySQL AIO不足启动失败问题分析

问题背景

昨天业务方在我司云上做mysql节点扩容的时候,出现新增节点启动失败的情况,查看error log发现报错原因为:io_setup() failed with EAGAIN after 5 attempt,详情如下图所示:

其实这个问题之前在云上数据库也出现过,直观结论就是系统aio资源不足了,但是由于时间等客观原因没有得到很好的解决,刚好趁这次机会,做一个分析和总结。 

问题分析

前言

在分析问题之前我们需要先简单了解一下linux asynchronous I/O System以及在mysql的innodb场景中有哪些用处。

先上官方解释:

Asynchronoes I/O (AIO) is a method for performing I/O operations so that the process that issued an I/O request is not blocked till the data is available. Instead, after an I/O request is submitted, the process continues to execute its code and can later check the status of the submitted request.Linux kernel provides only 5 system calls for performing asynchronoes I/O. Other AIO functions commonly descibed in the literature are implemented in the user space libraries and use the system calls internally. Some libraries can also emulate AIO functionality entirely in the user space without any kernel support.There are two main libraries in Linux that facilitate AIO, we will refer to them as libaio and librt (the latter one is a part of libc).

There are only 5 AIO system calls:
// aio 初始化
* int io_setup(unsigned nr_events, aio_context_t *ctxp);
// aio 销毁
* int io_destroy(aio_context_t ctx);
// aio 请求下发
* int io_submit(aio_context_t ctx, long nr, struct iocb *cbp[]);
* int io_cancel(aio_context_t ctx, struct iocb *, struct io_event *result);
// aio 请求回收
* int io_getevents(aio_context_t ctx, long min_nr, long nr,
			struct io_event *events, struct timespec *timeout);

简单理解Linux AIO(Asynchronous I/O)是一种异步I/O模型,允许应用程序在进行I/O操作时不会被阻塞。在传统的I/O模型中,当应用程序进行I/O操作时,它会等待I/O操作完成,然后再继续执行后续的代码。而在AIO模型中,应用程序可以继续执行其他任务,而不必等待I/O操作完成。

Linux 内核为上层应用提供aio异步非阻塞IO能力,通过牺牲部分请求延时,增强IO的并发吞吐量,在MySQL InnoDB场景中主要用来提高数据刷脏和写日志的并发吞吐性能。

具体问题分析

初步分析原因单台宿主机上面启动太多的mysql实例,linux系统aio占用过多,最终导致新增数据库实例启动失败。查看宿主机aio-max-nr设置最大值为64KB,当前已初始化使用64167,宿主机已启动29个数据库容器。

 

 详细分析mysql innodb aio初始化过程代码后总结一下几个问题原因,并尝试给出一些解决方案:

  1. aio-max-nr默认的64KB的设置对于mysql宿主机来说太小,可以适当调大,现在nvme ssd的磁盘性能支持到10w以上完全没问题;

  2. 单台宿主机启动太多小规格mysql实例,分析完innodb aio初始化过程发现,单实例aio初始数量取决于innodb_read_io_threads和innodb_write_io_threads这两个参数的设置(具体公式见详细代码分析过程),而云上mysql标准中这两个参数设置均为 CPU核数/2,上下限范围1-16,CPU在32C及以上规格读写IO线程限制在16,也就是说超过32C规格aio需求数量不再增量,所以调度部署几个大规格实例也能间接缓解aio使用压力;

  3. 实例规格太小,单机启动容器太多,从mysql aio计算公式可以看出每个实例除了读写IO线程的aio开销,还需要额外500-600左右的额外aio开销,所以小规格实例密集部署也会造成aio占用比较多

innodb aio初始化计算公式:

total number = ( innodb_read_io_threads+innodb_write_io_threads)*256 + 100(s_sync 8.0版本已移除)+ 256(s_ibuf) +256(s_log) +1(test support aio or not)

详细代码分析过程

从入口函数os_aio_init可以看出,innodb aio初始化主要和两个参数相关innodb_read_io_threads、innodb_write_io_threads,SRV_MAX_N_PENDING_SYNC_IOS为默认值100,8.0.30版本中移除了SRV_MAX_N_PENDING_SYNC_IOS部分(具体哪个版本移除有待确定)。

进一步分析os_aio_init函数,发现linux环境下每个读写线程设置aio limit=8*OS_AIO_N_PENDING_IOS_PER_THREAD(5.7和8.0都为32)= 256,即每个读写IO线程初始化256 占用aio event。

进一步分析start函数,确定各个模块aio初始化占用情况,这里Create函数原型为:

AIO::create(
    latch_id_t  id,          // 锁ID
    ulint       n,           // 初始化占用aio event数量
    ulint       n_segments)  // aio slot segment,每个线程一个segment 

1、首先调用is_linux_native_aio_supported校验是否支持native aio,占用一个aio event数量;

2、s_read aio初始化数量 = innodb_read_io_threads * 256:

3、云上mysql启动默认开启read_only,所以这里s_ibuf aio初始化数量 = 256 ,s_log aio 初始化数量 = 256

4、s_writes aio初始化数量 = innodb_read_io_threads * 256

在5.7版本中,还多了一个s_sync aio初始化,event数量SRV_MAX_N_PENDING_SYNC_IOS(即100)

得数据库aio初始化event数计算公式:

total number = ( innodb_read_io_threads+innodb_write_io_threads)*256 + 100(s_sync 8.0版本已移除)+ 256(s_ibuf) +256(s_log) +1(test support aio or not)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值