1.InnoDB存储引擎 AIO
insert into nkeys values (71,71,71,71,71);
Innodb的异步I/O,默认情况下使用linux原生aio,libaio。关于异步I/O的优势,可参考网文[18][19];libaio的限制,可见网文[17]。下面详细分析Innodb 异步I/O的处理步骤。
2.聚簇索引IO
insert操作,读取聚簇索引页面,函数调用流程:
buf_page_get_gen -> buf_read_page -> buf_read_page_low -> fil_io ->
os_aio_func(type, mode) ->
-
type = OS_FILE_READ; mode = OS_AIO_SYNC;使用os_aio_sync_array (其余的array包括:os_aio_read_array; os_aio_write_array; os_aio_ibuf_array; os_aio_log_array)
-
每个aio array,在系统启动时调用os0file.c::os_aio_init函数初始化
os_aio_init(io_limit,
srv_n_read_io_threads,
srv_n_write_io_threads,
SRV_MAX_N_PENDING_SYNC_IOS);
-
io_limit: 每个线程可以并发处理多少pending I/O
windows -> io_limit = SRV_N_PENDING_IOS_PER_THREAD = 32
linux -> io_limit = 8 * SRV_N_PENDING_IOS_PER_THREAD = 8 * 32 = 256
#define
SRV_N_PENDING_IOS_PER_THREAD OS_AIO_N_PENDING_IOS_PER_THREAD = 32 -
srv_n_read_io_threads
处理异步read I/O线程的数量
innobase_read_io_threads/innodb_read_io_threads:通过参数控制
因此系统可以并发处理的异步read page请求为:
io_limit * innodb_read_io_threads
os_aio_read_array = os_aio_array_create(n_read_segs * n_per_seg, n_read_segs);
异步I/O主要包括两大类:
-
预读page
需要通过异步I/O方式进行
-
主动Merge
Innodb主线程对需要merge的page发出异步读操作,在read_thread中进行实际merge处理
-
注:如何确定将哪些read io请求分配给哪些read thread?
-
首先,每个read thread负责os_aio_read_array数组中的一部分。
例如:thread0处理read_array[0, io_limit-1];thread1处理read_array[io_limit, 2*io_limit - 1],以此类推
-
os_aio_array_reserve_slot函数中实现了array的分配策略(array未满时)。
给定一个Aio read page,[space_id, page_no],首先计算local_seg(local_thd):
local_seg = (offset >> (UNIV_PAGE_SIZE_SHIFT + 6)) % array->n_segments;
然后从read_array的local_seg * io_limit处开始向后遍历array,直到找到一个空闲slot。
一来保证相邻的page,能够尽可能分配给同一个thread处理,提高aio(merge io request)性能;
二来由于是循环分配,也基本上保证了每个thread处理的io基本一致。
-
srv_n_write_io_threads
处理异步write I/O线程的数量
innobase_write_io_threads/innodb_write_io_threads:通过参数控制
因此系统可以并发处理的异步write请求为:
io_limit * innodb_write_io_threads
超过此限制,必须将已有的异步I/O部分写回磁盘,才能处理新的请求。
-
SRV_MAX_N_PENDING_SYNC_IOS
同步I/O array的slots个数,同步I/O不需要处理线程
-
log thread,ibuf thread个数均为1
os_aio_array_reserve_slot ->
-
在aio array中定位一个空闲array,aio前期准备工作
-
array已满
-
native aio: os_wait_event(array->not_full); native aio,等待not_full信号
-
非native aio:os_aio_simulated_wake_handler_threads;模拟唤醒
-
-
array未满
-
WIN_ASYNC_IO(windows AIO)
设置OVERLAPPED结构,使用的是Windows Overlapped I/O [5,6]
ResetEvent(slot->handle)
-
LINUX_NATIVE_AIO(Linux AIO)
设置iocb结构
然后根据type判断: io_prep_pread or io_prep_pwrite [7]
-
-
-
进行aio操作
-
type = OS_FILE_READ
-
use native aio
-
windows: ReadFile
-
Linux: os_aio_linux_dispatch(array, slot);
-
将async io请求发送至linux kernel
-
调用io_submit函数进行aio发送
iocb = &slot->control;
-
-
-
-
io_ctx_index = (slot->pos * array->n_segments) / array->n_slots;
ret = io_submit(array->aio_ctx[io_ctx_index], 1, &iocb);
-
iocb结构在slot之中;io_context结构,相同segment共用一个
-
use simulated aio
-
type = OS_FILE_WRITE
-
use native aio
-
windows: WriteFile
-
Linux: os_aio_linux_dispatch(array, slot)
-
-
use simulated aio
-
os_aio_windows_handle
-
特殊处理流程,windows下,若mode = OS_AIO_SYNC,则需要调用os_aio_windows_handle函数等待aio结束
-
判断当前是否为sync_array
-
若是,等待指定的slot aio操作完成: WaitForSingleObject
-
若不是,等待array中所有的aio操作完成: WaitForMultipleObjects
-
-
获取aio操作的结果
-
GetOverlappedResult
-
-
最后释放当前slot
-
os_aio_array_free_slot
-
-
os_aio_linux_handle
-
分析完os_aio_windows_handle函数,接着分析Linux下同样功能的函数:os_aio_linux_handle
-
无限循环,遍历array,直到定位到一个完成的I/O操作(slot->io_already_done)为止
-
若当前没有完成的I/O,同时有I/O请求,则进入os_aio_linux_collect函数
-
os_aio_linux_collect:从kernel中收集更多的I/O请求
-
调用io_getevents函数,进入忙等,等待超时设置为OS_AIO_REAP_TIMEOUT
/** timeout for each io_getevents() call = 500ms. */
#define OS_AIO_REAP_TIMEOUT (500000000UL)
-
若io_getevents函数返回ret > 0,说明有完成的I/O,进行一些设置,最主要是将slot->io_already_done设置为TRUE
slot->io_already_done = TRUE;
-
若系统I/O处于空闲状态,那么io_thread线程的主要时间,都在io_getevents函数中消耗。
-
-
-
3.Unique索引IO
与聚簇索引IO完全一致,因为二者都必须读取页面,不能进行Insert Buffer优化。
4.Non-unique索引IO
与聚簇索引IO不一致,区分流程在于函数buf_page_get_gen
buf_page_get_gen
if (mode == BUF_GET_IF_IN_POOL || mode == BUF_PEEK_IF_IN_POOL || mode == BUF_GET_IF_IN_POOL_OR_WATCH)
return NULL;
对于non-unique索引,此时mode = BUF_GET_IF_IN_POOL;若page在buffer pool中,则返回page,否则不立即读取page,进行insert buffer优化。
由于不会调用buf_read_page函数,因此不会产生物理IO。那么non-unique索引的页面何时会读入buffer pool,与insert buffer进行merge呢?详见 InnoDB Insert Buffer实现详解。
5.InnoDB存储引擎 Simulated AIO
在Linux系统上,MySQL数据库InnoDB存储引擎除了可以使用Linux自带的libaio之外,其内部还实现了一种称之为Simulated aio功能,用以模拟系统AIO实现(其实,Simulated aio要早于linux native aio使用在innodb中,可参考网文[16])。前面的章节,已经分析了InnoDB存储引擎对于Linux原生AIO的使用,此处,再简单分析一下 Innodb simulated aio的实现方式。
以下一段话摘自Transactions on InnoDB网站[16],简单说明了simulated aio在innodb中的处理方式。
… The query thread simply queues the request in an array and then returns to the normal working. One of the IO helper thread, which is a background thread, then takes the request from the queue and issues a synchronous IO call (pread/pwrite) meaning it blocks on the IO call. Once it returns from the pread/pwrite call, this helper thread then calls the IO completion routine on the block in question …
queue I/O request
os0file.c::os_aio_func;
os_aio_array_reserve_slot();
if (array->n_reserved == array_n_slots && !srv_use_native_aio)
os_aio_simuated_wake_handler_threads();
os_event_wait(array->not_full);
slot->… = …;
对于用户发起的read/write aio,simulated aio仍旧从对应的array中寻找空余slot,然后将aio request丢进此空闲slot,然后返回即可。
do I/O request by backend I/O thread
fil0fil.c::fil_aio_wait -> os0file.c::os_aio_simulated_handle
-
windows下处理异步I/O函数为os_aio_windows_handle;linux原生aio下为os_aio_linux_handle;而linux simulated aio下,对应的函数则是os_aio_simulated_handle
-
步骤一,遍历array,找出请求时间超过2S的I/O,优先处理,防止饥饿
-
步骤二,若步骤一没有定位到,则找出I/O request中,page_no最小的请求
-
步骤三,再次遍历array,判断是否存在与步骤一或步骤二定位到的page相邻的page request,相邻的请求全部一次处理,处理函数如下:
-
os_file_read/write -> os_file_read/write_func -> os_file_pread/pwrite -> pread/pwrite
-
调用pread/pwrite进行同步读/写
-
在读写结束之后,做些后续处理,标识对应的slot,I/O操作完成
总结:
Linux simulated aio,实现简单,基本采用的仍旧是同步IO的方式。相对于Linux native aio,simulated aio最大的问题在于:每个I/O请求,最终都会调用一次pread/pwrite进行处理(除非可以进行相邻page的合并),而Linux native aio,对于一个array,进行一次异步I/O处理即可。
6.AIO层次分析
-
InnoDB层面 (IO请求)
SRV_N_PENDING_IOS_PER_THREAD
一个InnoDB thread同时能够处理的异步I/O的数量
-
File System层面 (AIO queue)
fs.aio-max-nr
文件系统级别,可以同时进行的aio的数量上限
-
Disk层面 (IO queue)
nr_request
单个disk,可以并发处理的I/O请求的个数
若当前磁盘为逻辑盘,那么真实的nr_request = nr_request * disks in raid
注:
彭立勋 (13:42:00):
FusionIO处理不是普通盘那样,普通磁盘消耗IO队列是匀速的,FIO是抖动式的
何登成 (13:42:50):
难道说要将aio队列调小,消除FIO的抖动?
彭立勋 (13:43:40):
例如FusionIO的处理能力是每10ms 100个IO,IO队列里有200个IO,FusionIO会一次拿走100,10ms内不再从IO队列里拿东西,然后再拿走100个
SAS盘是匀速的,每处理完一个就拿一个
何登成 (13:44:34):
那也没法解释为什么是10S抖动一次啊?
彭立勋 (13:44:39):
然后nr_request有个算法,如果IO队列达到2/3时,就认为系统阻塞了,就没响应了
彭立勋 (13:45:49):
所以如果AIO队列太长,一次写到磁盘IO队列上的东西太多,碰上FusionIO,就容易出现达到2/3,虽然很快就消失了,但是还是能看到突然阻塞这种情况
霸爷建议,观察nr_request用了多少,AIO队列长度保持磁盘IO队列的2/3以内
何登成 (13:47:16):
哦,有点理解了
就是要aio的队列最大长度,不超过nr_request的2/3
彭立勋 (13:47:22):
aio_nr是FS级的,nr_request是Disk级的
是的
霸爷就是这意思,只对FusionIO有效,SAS盘不是这样的
SAS盘尽管往里面堆
7.Linux AIO增强
InnoDB使用的linux native aio,仅仅支持以O_DIRECT方式打开的文件。针对此情况,大神Jens Axboe做了进一步的优化,提供了Buffered async IO,具体情况可参考其个人主页上的一篇文章:Buffered async IO。
8.软中断的影响
上面提到的两个问题点,是InnoDB引擎内部的,其实mysql在运行过程中,还有一个极大的与操作系统与硬件相关的问题点——(软)中断的处理。关于软中断的原理以及Linux下软中断的实现,可参考[25][26]。
在mysql系统中,最主要的软中断有两类:Disk软中断;网卡软中断。而软中断带来的问题是:所有的软中断由一个CPU集中处理,导致单个CPU利用率增加,同时处理软中断的性能下降。一个典型的例子如下:
mpstat -P ALL 2 3
Average: CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
Average: all 1.07 0.00 0.37 0.12 0.08 0.04 0.00 98.31 1555.33
Average: 0 6.28 0.00 0.83 0.99 0.66 0.17 0.00 91.07 1555.00
Average: 1 0.00 0.00 0.17 0.00 0.00 0.00 0.00 99.83 0.00
Average: 2 0.00 0.00 0.17 0.00 0.00 0.00 0.00 99.83 0.00
Average: 3 0.82 0.00 1.31 0.00 0.00 0.00 0.00 97.88 0.00
上例中,intr/s列,表示CPU在internal时间段(2S)内,每秒CPU接收的中断的次数(intr /total)*100;%soft列,表示在internale时间段内,软中断时间(softirq/total)*100。可以看出,每秒总得中断 次数为1555.33,而CPU 0就处理了1555个,中断的处理分配极度不均衡。
解决的方案也很明确,将软中断处理分布到系统所有的CPU中即可。
关于网卡软中断对于MYSQL数据库性能的影响及解决方案,可参考网文 MYSQL数据库网卡软中断不平衡问题及解决方案 [22]。除此之外,Google与Facebook都曾经针对网卡软中断,做过相应的优化[23][27].
而对于Disk软中断,据我所知2002年一本漫画闯天涯同学,也对Linux做了优化,能够保证Disk软中断的处理在所有CPU中平均分布:block: improve rq_affinity placement。
关于中断(包括软硬中断),Linux系统已经做了大量的优化,简单归纳起来,主要有以下一些:
-
高级可编程中断控制(Advanced Programmable Interrupt Controller, APIC) [30]
-
中断亲和力(SMP IRP Affinity) [28][29]
-
IRQ Balance [32]
-
MSI & MSI-X [34][35][36]
-
网卡软中断处理[22][23][27]
-
磁盘软中断处理[37]
-
中断监控 [33]