innodb学习笔记(一) aio的使用
这里先以mysql 5.7.26版本进行学习。
一. 四种IO线程及线程数量
分为读线程,写线程,log io线程和buf io线程,重点在innobase/os/Osofile.cc这个文件里。
1.1 aio读写线程
innodb可以配置aio线程的数量,在图1-1某线上实例的my.cnf中可以看到读线程和写线程数量的配置,这里一例读写线程数量各自为8。后文都以读8写8为例说明。
图 1-1 某线上实例线程数量配置
实际代码中srv0start.cc负责aio的初始化,这里实际对应srv_n_read_io_threads和srv_n_write_io_threads两个参数分别指定负责读IO的线程数量和负责写IO的线程数量;
当然除了指定的读线程和写线程外,还有一个log io thread和ibuf io thread。所以实际的io线程总数srv_n_file_io_threads就是:
srv_n_file_io_threads = srv_n_read_io_threads + srv_n_write_io_threads + 2
以上这些逻辑在innobase_start_or_create_for_mysql()初始化中可以看到。
综上,一个mysql实例中有:
- 4种不同的IO线程;
- n个read io线程,n个write io线程,一个log io线程,一个buf io线程;
二. aio管理结构
核心是定义在os0file.cc中的class AIO结构,如下图列出了其中所有的类成员和部分重要类方法。
图 2-1 aio类
其实全局来看AIO结构是一个工厂,并不会有实体,该工厂可以根据参数分别创建read io,write io,log io和ibuf io这四种不同的AIO实例,这些实例被创建好后赋给相应的static AIO *指针,之后在db实例的整个运行过程中就可以通过诸如s_reads, s_writes这样的static指针来调用相应的AIO实例。
2.1 AIO结构中重要的成员
- m_mutex
对于wirte和read类型的AIO,会有多个线程来操作AIO实例,因此需要mutex进行互斥;
- m_slots
一个AIO包含的所有的slot实例,存储在该向量中,每个slot代表一个io请求,可能是读,也可能是写。slot的数量为相应的线程数量 n 乘以系统规定的该类线程允许的pending aio的数量,其中线程数量n在上一节已经给出,而该类线程允许pending aio的数量为硬编码n * OS_AIO_N_PENDING_IOS_PER_THREAD为256;
对于read/write io类型的AIO,slot数量为线程数n * 256,这些slot需要均分成m_n_segments份,其实m_n_segments就是线程数量;
- m_n_segments
所有的slot会被分隔成m_n_segments份,其实就是io线程数量,一个io线程可以以io_getevents等待在任一segment上,其实一个segment就对应一个io_ctx;可以推断,针对某一种io线程,比如write io数量为8,那么m_n_segments值就为8,slots被分隔成8个segment,每个segment包含的slot数量为256;从后续代码可以看到,每个io_ctx在被io_setup和io getevents时,参数nr和max_nr都为256;
图 2-2 slot的分割
实际上看起来分成多个segments的主要目的除了提前分配好m_n_segments个io_ctx外,可以把物理地址相近的io放入到同一segments中方便可能存在的io merge,如reserve_slot()中所示;
但是,reserve_slot()中选择了一个segment后,io并不一定真的能放入这个segment中,只是从这个segment开始搜索第一个未被占用的slot而已;
- m_not_full
条件变量,当当前AIO的slots从full变成not full时就释放这个条件变量,表示有空余的slot可以用来提交io请求了,如reserve_slot()中所示;
- m_is_empty
条件变量,当当前AIO的被reserved的slot数量从0变成1时该条件会被hold,见reserve_slot();当reserved的slot数量从1变成0时会被释放,见AIO::release();
从代码来看,只有s_write类型的AIO会存在对该条件变量的wait,主要是log file和db wirte file的flush的前置操作;
- m_n_reserved
该AIO中被占用的slot总数,维护这个数字主要是做为m_not_full和m_is_empty两个条件变量操作的依据;
- m_aio_ctx
n_segments个aio上下文组成的数组,正好每个IO线程对应一个;
- m_events
n_slots个aio_event组成的数组,aio event完成后,io_getevents将io_event保存的位置;
- s_log/s_writes/s_reads/s_ibuf
四种不同类型io线程对应的AIO实例;
2.2 Slot结构
一个Slot结构代表一个io请求。Slot结构非常容易理解,该IO相关的一些信息。
图 2-3 slot结构
三. AIO初始化
aio的初始化从os_aio_init,可以分为两个范畴:
- AIO范畴:分别为各种io类型创建AIO实例,也就是调用AIO::create()函数,对于每个AIO实例,就是执行构造函数和init成员方法;
- linux aio范畴:对于每一个AIO实例内部,io_setup准备m_n_segment个aio_ctx,也就是每个io线程对应一个;每个aio_ctx的max_event值为该AIO的m_slots.size()除以m_n_segment,其实就是256;这些操作是在init_linux_native_aio()中完成;
以上两个操作还包含了为AIO实例以及Slot实例的成员变量赋初始值。
图 3-1 aio初始化函数调用
四. AIO线程逻辑
在代码中多处看到诸如# ifdef UNIV_PFS_THREAD这样的宏,这里pfs是指performance schema instrumented,这是用于监控MySQL server在一个较低级别的运行过程中的资源消耗、资源等待等情况的一个模块,参考https://blog.csdn.net/qq_35068291/article/details/105921515。
AIO逻辑当然包括提交IO请求(submit)和IO收割(getevents)两部分,实际上innodb里的AIO逻辑主要是在收割上,前文所述的IO线程都是在做收割工作,所以我们先来看下收割逻辑。
4.1 收割
AIO线程主函数是io_handler_thread,在innobase_start_or_create_for_mysql( )中可以看到create了srv_n_file_io_threads个AIO线程均是以io_handler_thread为线程主函数。
图 4-1 aio收割逻辑
如图4-1所示,其实主函数就是不断循环调用fil_aio_wait函数,fil_aio_wait则负责实际的对segment中aio_ctx的io_getevents和善后工作。
其中os_aio_handler是实际的收割动作,segment实际指定了aio_ctx,其他参数其实都是输出型结果参数。
4.2 提交
linux_dispatch通过io_submit提交aio,层层回推,aio的submit入口是fil_io函数。
fil_io一个主要的使用场景是刷脏页,逻辑比较复杂,准备后面专门分析,这里画了一个主要的逻辑图。
https://www.processon.com/view/link/5f6602cee401fd2ad7eb852