转载

Mysql Innodb中的Linux native异步I/O(一) 内存结构的初始化

水平有限,有误请指出

一、前言

在5.7中Innodb异步I/O的内存结构发生了一些变化特别是异步I/O数组和以前的结构体不同变为了类叫做AIO类但是换汤不换药只是将一些方法进行了封装,而异步i/o实际的请求放到了Slot结构体它们分别对应了5.6 os_aio_array_t和os_aio_slot_t,这里不准备详细介绍每一个属性的含义,因为在内核月报中淘宝已经给出,5.7基本也是一样的连接如下:
http://mysql.taobao.org/monthly/2017/07/10/
同时我这里也是讨论关于Linux native的部分对于innodb自己模拟的异步I/O不做分析,因为用得不多,并且自己能力也有限。但是这里还是需要明确几个概念


本文简书地址
http://www.jianshu.com/p/dd7e1e560af0


二、几个基本概念

  • 什么是Linux native I/O
    参考我的文章:
    http://blog.itpub.net/7728585/viewspace-2147684/
    或者参考其他文章
  • MYSQL中的异步I/O线程
    我以前一直搞不清楚这几个线程的作用,为了搞清楚这个我才决定好好学习一下异步I/O
    如下面的参数设置
mysql> show variables like '%io_threads%';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_read_io_threads  | 2     |
| innodb_write_io_threads | 2     |
+-------------------------+-------+ 

我在本数据中实际设置了2个read 异步i/o线程和2个write异步I/O线程此外都包含一个log和ibuf异步I/O线程在数据库中我们也可以查询到这6个异步I/O线程

mysql> select a.thd_id,b.THREAD_OS_ID,a.user ,a.conn_id,b.TYPE,a.source,a.program_name from sys.processlist a,performance_schema.threads b where b.thread_id=a.thd_id and  user  like '%io%';
+--------+--------------+------------------------+---------+------------+--------------------+--------------+
| thd_id | THREAD_OS_ID | user                   | conn_id | TYPE       | source             | program_name |
+--------+--------------+------------------------+---------+------------+--------------------+--------------+
|      3 |        14059 | innodb/io_ibuf_thread  |    NULL | BACKGROUND | NULL               | NULL         |
|      4 |        14060 | innodb/io_log_thread   |    NULL | BACKGROUND | sync0debug.cc:1296 | NULL         |
|      5 |        14061 | innodb/io_read_thread  |    NULL | BACKGROUND | NULL               | NULL         |
|      6 |        14062 | innodb/io_read_thread  |    NULL | BACKGROUND | NULL               | NULL         |
|      7 |        14063 | innodb/io_write_thread |    NULL | BACKGROUND | sync0debug.cc:1296 | NULL         |
|      8 |        14064 | innodb/io_write_thread |    NULL | BACKGROUND | NULL               | NULL         |
+--------+--------------+------------------------+---------+------------+--------------------+--------------+ 
  • AIO类、线程、Slot的关系

首先一个AIO类对应了一个类型的异步,比如ibuf/log/read/write都对应一个AIO类,并且在类的最后用一个类的静态全局成员进行指向如下:

 /** Insert buffer */
   static AIO*     s_ibuf;
   /** Redo log */
   static AIO*     s_log;
   /** Reads */
   static AIO*     s_reads;
   /** Writes */
   static AIO*     s_writes; 

而我们的异步I/O线程实际上有6个也就是s_reads包含了2个线程/s_writes包含了2个线程,那么线程引入了一个叫做local segment的概念,实际上每一个线程对应了一个local segment,而在AIO下面挂的就是一个Slot的vertor数组,数组的大小和每种类型的线程个数(local segment)和每个线程最大的Slot有关,看源码中对最大的Slot的定义如下:

8 * OS_AIO_N_PENDING_IOS_PER_THREAD 

其中宏定义OS_AIO_N_PENDING_IOS_PER_THREAD=32

那么对于s_ibuf和s_log因为只有一个线程(local segment)那么就有256个Slot,而s_reads和s_writes当前我的数据库各有2个线程(local segment)那么就有2*256=512个Slot.

  • global segment
    这个概念主要和模拟的异步I/O有关,如果我当前有6个异步I/O线程那么global segment就是6,因为在进行初始化调用AIO::start的时候其编号总是固定的及0和1对应了然后是read和write线程个数,那么由global segment到local segment的换算也变得简单了可以参考AIO::get_segment_no_from_slot.

如果没有显示指定本文所有segment均指local segment



三、内存结构的初始化

整个内存结构的初始化是从由innobase_start_or_create_for_mysql调用的下面代码开始的如下:

点击(此处)折叠或打开

  1. if (!os_aio_init(srv_n_read_io_threads,
  2.             srv_n_write_io_threads,
  3.             SRV_MAX_N_PENDING_SYNC_IOS)) {

  4.        ib::error() << "Cannot initialize AIO sub-system";

  5.        return(srv_init_abort(DB_ERROR));
  6.    }
实际上就是调用了os_aio_init,接下来我们来进行逐层的分析

1、os_aio_init 由innobase_start_or_create_for_mysql ()调入

本数据库调用栈帧:


点击(此处)折叠或打开

  1. #0 os_aio_init (n_readers=2, n_writers=2, n_slots_sync=100) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6734
  2. #1 0x0000000001b8dde1 in innobase_start_or_create_for_mysql () at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/srv/srv0start.cc:1792
源码及注释如下:

点击(此处)折叠或打开

  1. bool
  2. os_aio_init(
  3.    ulint n_readers,
  4.    ulint n_writers,
  5.    ulint n_slots_sync)
  6. {
  7.    /* Maximum number of pending aio operations allowed per segment */
  8.    ulint limit = 8 * OS_AIO_N_PENDING_IOS_PER_THREAD; //这里我们发现了limit的定义也就是
  9.                                                         //一个线程(local segments)包含的slot个数及256
  10. .....
  11.    return(AIO::start(limit, n_readers, n_writers, n_slots_sync));
  12. }

这个函数的主要功能就是调用AIO::start下面我们进行学习。

2、 AIO::start 由os_aio_init调入

本数据库调用栈帧:


点击(此处)折叠或打开

  1. #1 0x0000000001a7db18 in AIO::start (n_per_seg=256, n_readers=2, n_writers=2, n_slots_sync=100)
  2.    at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6610
  3. #2 0x0000000001a7e289 in os_aio_init (n_readers=2, n_writers=2, n_slots_sync=100) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6762
源码及注释如下:

点击(此处)折叠或打开

  1. bool
  2. AIO::start(
  3.    ulint n_per_seg, //每个segment的slot数量
  4.    ulint n_readers, //多少个异步读I/O线程
  5.    ulint n_writers, //多少个异步写I/O线程
  6.    ulint n_slots_sync)
  7. {
  8. #if defined(LINUX_NATIVE_AIO)
  9.    /* Check if native aio is supported on this system and tmpfs */
  10.    //这里根据参数innodb_use_native_aio设置和是否支持native aio测试进行综合判断,即便
  11.    //参数设置为ON,但是不支持libaio也会将srv_use_native_aio设置为FLASE
  12.    if (srv_use_native_aio && !is_linux_native_aio_supported()) {

  13.        ib::warn() << "Linux Native AIO disabled.";//这里出现一个经常看到警告信息native aio不可用

  14.        srv_use_native_aio = FALSE;
  15.        
  16.    }
  17. #endif /* LINUX_NATIVE_AIO */

  18.    srv_reset_io_thread_op_info(); /* 重置线程状态设置为not start */

  19.  /* 这里开始初始化AIO read线程 我这里设置是 nreaders=2个read线程 n_per_seg=每个线程 256个slot */
  20.    s_reads = create(
  21.        LATCH_ID_OS_AIO_READ_MUTEX, n_readers * n_per_seg, n_readers);

  22.    if (s_reads == NULL) {
  23.        return(false);
  24.    }
  25.  
  26.  /* 这里进行只读检测 如果是只读则log 和 ibuf 异步线程不启用
  27.     由逻辑srv_read_only_mode ? 0 : 2 进行控制
  28.     下面主要开始设置线程的名字如果没有log和ibuf则
  29.     readers从下标0开始否则从下标2开始 */
  30.  Array of English strings describing the current state of an i/o handler thread */
  31. const char* srv_io_thread_op_info[SRV_MAX_N_IO_THREADS];
  32. const char* srv_io_thread_function[SRV_MAX_N_IO_THREADS];
  33.     */
  34.    ulint start = srv_read_only_mode ? 0 : 2;
  35.    ulint n_segs = n_readers + start;
  36.    
  37.    /* 0 is the ibuf segment and 1 is the redo log segment. */
  38.    for (ulint i = start; i < n_segs; ++i) {
  39.        ut_a(i < SRV_MAX_N_IO_THREADS);
  40.        srv_io_thread_function[i] = "read thread";
  41.    }

  42.    ulint n_segments = n_readers;


  43. /* 如果没有设置只读,这里开始不会初始化ibuf和log aio结构 */

  44.    if (!srv_read_only_mode) {

  45.        s_ibuf = create(LATCH_ID_OS_AIO_IBUF_MUTEX, n_per_seg, 1);

  46.        if (s_ibuf == NULL) {
  47.            return(false);
  48.        }

  49.        ++n_segments;

  50.        srv_io_thread_function[0] = "insert buffer thread";

  51.        s_log = create(LATCH_ID_OS_AIO_LOG_MUTEX, n_per_seg, 1);

  52.        if (s_log == NULL) {
  53.            return(false);
  54.        }

  55.        ++n_segments;

  56.        srv_io_thread_function[1] = "log thread";

  57.    } else {
  58.        s_ibuf = s_log = NULL;
  59.    }

  60. /* 依然是一样的方式初始化write thread AIO结构 */

  61.    s_writes = create(
  62.        LATCH_ID_OS_AIO_WRITE_MUTEX, n_writers * n_per_seg, n_writers);

  63.    if (s_writes == NULL) {
  64.        return(false);
  65.    }

  66.    n_segments += n_writers; //这里我们得到最终的GLOBAL SEGMENT = 2+1+1+2 = 6

  67.    for (ulint i = start + n_readers; i < n_segments; ++i) {
  68.        ut_a(i < SRV_MAX_N_IO_THREADS);
  69.        srv_io_thread_function[i] = "write thread";
  70.    }

  71.    ut_ad(n_segments >= static_cast<ulint>(srv_read_only_mode ? 2 : 4));

  72.    s_sync = create(LATCH_ID_OS_AIO_SYNC_MUTEX, n_slots_sync, 1);/*这个线程功能还需要看看n_slots_sync=100 */

  73.    if (s_sync == NULL) {

  74.        return(false);
  75.    }

  76.    os_aio_n_segments = n_segments; //=6

  77.    os_aio_validate();
  78.  //开始分配event,他是cond和mutex的封装
  79.    os_aio_segment_wait_events = static_cast<os_event_t*>(
  80.        ut_zalloc_nokey(
  81.            n_segments * sizeof *os_aio_segment_wait_events)); //这里分配n_segments 个数的条件变量内存这里是6个
  82. /*
  83.   Array of events used in simulated AIO
  84. static os_event_t* os_aio_segment_wait_events = NULL;
  85.  最后指针给了这样一个内部全局静态变量
  86. */


  87.    if (os_aio_segment_wait_events == NULL) {

  88.        return(false);
  89.    }

  90. /*
  91.    对其进行初始化这里我们明确的看出每一个SEGMENTS 也就是每一个线程都对应一个条件变量和MUTEX
  92.    他的具体作用和模拟异步I/O有关如AIO::wake_simulated_handler_thread调用
  93. */
  94.    for (ulint i = 0; i < n_segments; ++i) {
  95.        os_aio_segment_wait_events[i] = os_event_create(0);
  96.    }

  97.    os_last_printout = ut_time();

  98.    return(true);
  99. }

这里我们可以看到实际上还是调用核心函数AIO::create,在AIO::create调用成果后就将各个类型的AIO对象的指针赋予给了几个类静态全局指针用于后面调用

3、AIO::create 由AIO::start 调入

本数据库调用栈帧:

点击(此处)折叠或打开

  1. #0 AIO::create (id=LATCH_ID_OS_AIO_READ_MUTEX, n=512, n_segments=2) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6529
  2. #1 0x0000000001a7db18 in AIO::start (n_per_seg=256, n_readers=2, n_writers=2, n_slots_sync=100) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6610
源码及注释如下:

点击(此处)折叠或打开

  1. AIO*
  2. AIO::create(
  3.    latch_id_t id,
  4.    ulint n, //某个类型AIO对象应该包含的SLOT数量
  5.    ulint n_segments) //线程数量(segment)
  6. {
  7.    if ((n % n_segments)) { //这里先做了一个保障校验n是否是n_segments的倍数

  8.        ib::error()
  9.            << "Maximum number of AIO operations must be "
  10.            << "divisible by number of segments";

  11.        return(NULL);
  12.    }
  13.    AIO* array = UT_NEW_NOKEY(AIO(id, n, n_segments)); //功能1、调用构造函数AIO(id, n, n_segments)
  14.    if (array != NULL && array->init() != DB_SUCCESS) { //功能2、调用array->init()

  15.        UT_DELETE(array);

  16.        array = NULL;
  17.    }
  18.    return(array);
  19. }

我们发现本函数有2个主要功能

  • 1、调用构造函数AIO(id, n, n_segments)
  • 2、调用array->init()
    所以我们需要分别讨论

首先来看AIO(id, n, n_segments)

4、AIO::AIO 由AIO::create 调入


本数据库调用栈帧:

点击(此处)折叠或打开

  1. #0 AIO::AIO (this=0x32ea658, id=LATCH_ID_OS_AIO_READ_MUTEX, n=512, segments=2) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6396
  2. #1 0x0000000001a7d862 in AIO::create (id=LATCH_ID_OS_AIO_READ_MUTEX, n=512, n_segments=2)
  3.    at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6538

源码及注释如下:

点击(此处)折叠或打开

  1. AIO::AIO(
  2.    latch_id_t id,
  3.    ulint n, //某个类型AIO对象应该包含的SLOT数量
  4.    ulint segments)//线程数量(segment)
  5.    :
  6.    m_slots(n), //定义出多少个slot这里是512个因为我有2个io_read线程每个线程256个slot,分配内存
  7.    m_n_segments(segments), //多少个segments 我是2个io_read也就是2个
  8.    m_n_reserved(),//设置为0
  9.    m_aio_ctx(),//设置为NULL
  10.    m_events(m_slots.size()) //完成events数组大小设置为slot的个数
  11. {
  12. ....
  13.    mutex_create(id, &m_mutex); //根据传入的ID建立mutex 本MUTEX 保护多个线程同时使用本数组

  14.    m_not_full = os_event_create("aio_not_full");//建立所谓的event,在这个event中封装了条件变量cond和mutex
  15.    m_is_empty = os_event_create("aio_is_empty");//建立所谓的event,在这个event中封装了条件变量cond和mutex

  16.    memset(&m_slots[0], 0x0, sizeof(m_slots[0]) * m_slots.size());//将整个slot内存空间全部清0
  17. #ifdef LINUX_NATIVE_AIO
  18.    memset(&m_events[0], 0x0, sizeof(m_events[0]) * m_events.size());//将整个events内存空间全部清0,他就是io_getevents调用需要的
  19. #endif /* LINUX_NATIVE_AIO */

  20.    os_event_set(m_is_empty); //通过brocast唤醒,所有堵塞在m_is_empty上的线程进行处理
  21. }

经过本函数我们发现在AIO这个结构体中的成员基本都进行了初始化
m_slots/m_n_segments/m_n_reserved/m_aio_ctx/m_events/m_mutex/m_not_full/m_is_empty
只是这些某些还没有意义比如m_aio_ctx

接下来我们看第二个功能 AIO::init()

5、AIO::init() 由AIO::create调入

本数据库调用栈帧:

点击(此处)折叠或打开

  1. #0 AIO::init (this=0x32ea658) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6493
  2. #1 0x0000000001a7d8a2 in AIO::create (id=LATCH_ID_OS_AIO_READ_MUTEX, n=512, n_segments=2)
  3.    at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6540

源码及注释如下:

点击(此处)折叠或打开

  1. /** Initialise the array */
  2. dberr_t
  3. AIO::init()
  4. {
  5.    ut_a(!m_slots.empty());//这里断言是否为空,不可能为空除非遇到故障

  6. #ifdef _WIN32
  7.    ut_a(m_handles == NULL);

  8.    m_handles = UT_NEW_NOKEY(Handles(m_slots.size()));
  9. #endif /* _WIN32 */

  10.    if (srv_use_native_aio) { //这个并非参数设置而是前面说的参数设置和innodb检测是否支持native aio的综合考虑
  11. #ifdef LINUX_NATIVE_AIO
  12.        dberr_t err = init_linux_native_aio();//功能1 如果开启了innodb_use_native_aio参数并且支持native aio进行调用init_linux_native_aio()

  13.        if (err != DB_SUCCESS) {
  14.            return(err);
  15.        }

  16. #endif /* LINUX_NATIVE_AIO */
  17.    }

  18.    return(init_slots()); //功能2 调用init_slots()初始化slot内存结构

我们发现本函数有2个主要功能

  • 1、如果开启了innodb_use_native_aio参数并且支持native aio进行调用init_linux_native_aio()
  • 2、调用init_slots()初始化slot内存结构

下面我们先看看AIO::init_linux_native_aio()调用

6、AIO::init_linux_native_aio() 由 AIO::init调入

本数据库调用栈帧:

点击(此处)折叠或打开

  1. #0 AIO::init_linux_native_aio (this=0x32ea658) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6460
  2. #1 0x0000000001a7d701 in AIO::init (this=0x32ea658) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6503
源码及注释如下:

点击(此处)折叠或打开

  1. /** Initialise the Linux Native AIO interface */
  2. dberr_t
  3. AIO::init_linux_native_aio()
  4. {
  5.    /* Initialize the io_context array. One io_context
  6.    per segment in the array. */

  7.    ut_a(m_aio_ctx == NULL);

  8.    m_aio_ctx = static_cast<io_context**>(
  9.        ut_zalloc_nokey(m_n_segments * sizeof(*m_aio_ctx)));//到这里了我们知道AIO类的属性已经进行了初始化m_n_segments就是本AIO对象包含的线程数量
  10.                                                            //ibuf和redo异步为1个,write和read异步线程本系统为2个这由参数控制,这里为io_context_t
  11.                                                            //也就是一个线程对应一个io_context_t这是Linux native aio必须的

  12.    if (m_aio_ctx == NULL) {
  13.        return(DB_OUT_OF_MEMORY);
  14.    }

  15.    io_context** ctx = m_aio_ctx;
  16.    ulint max_events = slots_per_segment(); //这里返回每个线程最大的event个数用于初始化io_context_t结构体就是return(m_slots.size() / m_n_segments)
  17.                                                        //及256个

  18.    for (ulint i = 0; i < m_n_segments; ++i, ++ctx) { //进行初始化对每个线程的io_context_t调用io_steup进行初始化其队列最大event个数为
  19.                                                        //return(m_slots.size() / m_n_segments); 也就是256

  20.        if (!linux_create_io_ctx(max_events, ctx)) { //linux_create_io_ctx 主要功能就是初始化io_context_t
  21.            /* If something bad happened during aio setup
  22.            we should call it a day and return right away.
  23.            We don't care about any leaks because a failure
  24.            to initialize the io subsystem means that the
  25.            server (or atleast the innodb storage engine)
  26.            is not going to startup. */
  27.            return(DB_IO_ERROR);
  28.        }
  29.    }

  30.    return(DB_SUCCESS); //最后返回成功,这样io_context_t也就是AIO结构体中的m_aio_ctx得到了初始化
  31. }

到这里io_context_t已经分配,接下来调用AIO::init_slots()我们进行分析


7、AIO::init_slots() 由 AIO::init调入

本数据库调用栈帧:


点击(此处)折叠或打开

  1. #0 AIO::init_slots (this=0x32ea658) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6421
  2. #1 0x0000000001a7d71b in AIO::init (this=0x32ea658) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/os/os0file.cc:6512

源码及注释如下:


点击(此处)折叠或打开

  1. /** Initialise the slots */
  2. dberr_t
  3. AIO::init_slots()
  4. {
  5.    for (ulint i = 0; i < m_slots.size(); ++i) {
  6.        Slot& slot = m_slots[i]; //使用引用指向第i个元素

  7.        slot.pos = static_cast<uint16_t>(i); //分别初始化为0到m_slots.size()也就是每个slot进行了编号编号为pos

  8.        slot.is_reserved = false; //初始化

  9. #ifdef WIN_ASYNC_IO //下面是WINDOW的处理不分析

  10.        slot.handle = CreateEvent(NULL, TRUE, FALSE, NULL);

  11.        OVERLAPPED* over = &slot.control;

  12.        over->hEvent = slot.handle;

  13.        (*m_handles)[i] = over->hEvent;

  14. #elif defined(LINUX_NATIVE_AIO)

  15.        slot.ret = 0; //ret设置0

  16.        slot.n_bytes = 0; //n_bytes设置为0

  17.        memset(&slot.control, 0x0, sizeof(slot.control)); //这里对iocb结构进行清0操作,其实在AIO::AIO中已经清0了因为iocb并不是指针而是实际的内存空间

  18. #endif /* WIN_ASYNC_IO */
  19.    }

  20.    return(DB_SUCCESS);
  21. }


到这里Linux native 异步I/O的iocb已经分配个数为256*segment 并且初始化0完成。

四、初始化完成后内存图

如下一张简图表示了初始化完成后的内存图:


Mysql Innodb中的Linux native异步I/O(一) 内存结构的初始化


五、后记

初始化完成后接下来就是如何调用了,这个还需要仔细的分析。再开一篇文章进行分析。
那么最后写一下如果要用Linux native AIO 需要满足的条件

  • 1、innodb_use_native_aio = ON
  • 2、libaio安装了
  • 3、innodb_flush_method O_DIRECT

缺一不可,这些条件很容易达到

作者微信:


Mysql Innodb中的Linux native异步I/O(一) 内存结构的初始化





正文到此结束
Loading...