美文网首页
MySQL:刷脏相关部分

MySQL:刷脏相关部分

作者: 重庆八怪 | 来源:发表于2023-11-12 16:15 被阅读0次

    这里记录一下刷脏和IO代码的简要流程(8.0.23),仅供参考。先简要总结一下:

    • 当大于默认8192(innodb_lru_scan_depth*innodb_buffer_pool_instances)的时候只有flush list刷脏,flush list刷脏使用异步,也就是buffer pool 的page会尽量填充,不做LRU淘汰。
    • 当小于默认8192(innodb_lru_scan_depth*innodb_buffer_pool_instances)的时候会触发LRU扫描,扫描的时候需要释放page,如果page是干净的则直接释放放入free list,且做LRU淘汰,如果
      page不是干净的则需要进行lru刷脏,LRU刷脏同样使用的是异步IO,完成后且放入free list,且需要做LRU淘汰。
    • 刷盘总是先写入到DBLWR,这是同步IO的方式,如果不是O_D打开的文件还需要做对DBLWR做FSYNC。
    • 刷盘写入数据文件由DBLWR部分完成,为异步IO,异步IO主要为前台thread 发起io_submit,后台异步IO线程堵塞等待结果,然后处理完成的一些动作,比如检查page的IO状态,数据文件的fsync操作,刷盘后一个更加重要的动作是异步IO线程还要完成内存的维护,比如对于flush list刷脏需要从flush list链表中删除,对于lru刷脏则额外还需要从page的hash结构和LRU链表中删除并且返回给free list成为可用的空闲page。
    • 异步IO通常在Linux上为Linux native aio。
    • 读取为同步IO,预读为异步IO,刷脏为异步IO,写DBLWR为同步IO。
    • DBLWR的默认情况下为2个物理文件,其中一个要大一些,固定为512*16K,为s_single_segments类型的segment。

    一、刷脏

    当cleaner线程需要刷脏的时候,先计算需要刷脏的page数量,然后调用pc_flush_slot进行刷脏,下面是一些主要流程供参考,

    pc_flush_slot
      获取一个instance进行刷盘
    -----------------LRU 淘汰如果是脏页则需要刷脏,淘汰中还要判断是否为state 页
      ->buf_flush_LRU_list       
        首先进行LRU刷盘,!!!主要是如果free_len 小于了srv_LRU_scan_depth需要开启LRU淘汰或者刷盘
        ->buf_flush_do_batch(buf_pool, BUF_FLUSH_LRU, scan_depth, 0, &n_flushed);
          标记为LRU刷盘,带入扫描深度,返回刷盘的page数量  srv_LRU_scan_depth
          ->buf_flush_batch(buf_pool, type, min_n, lsn_limit)
            min_n为带入的scan_depth参数
            ->buf_flush_start
              ->buf_pool->init_flush[flush_type] = TRUE
                正在进行BUF_FLUSH_LRU刷脏
            -> switch (flush_type)
              case BUF_FLUSH_LRU:(这里为LRU刷脏)
                mutex_enter(&buf_pool->LRU_list_mutex);
                ->count = buf_do_LRU_batch(buf_pool, min_n)
                  这里的count是需要返回的刷新的page数量
                  -> buf_flush_LRU_list_batch(buf_pool, max - count)
                     max - count普通情况就是max  
                     ------见下1.1-----这个就比较复杂了,需要判断是否为state page/干净page/脏页,前面2个自己维护内存,脏页刷盘后异步IO完成内存维护               
                mutex_exit(&buf_pool->LRU_list_mutex);
          ->buf_flush_batch
            ->count = buf_do_LRU_batch(buf_pool, min_n)
              调用LRU刷新
          ->buf_flush_end
    -------------------FLUSH LIST刷脏,相对简单判断也比较少,因为flush list都是按照顺序排列的脏页,而且不释放page只负责刷脏,那么在压力小的情况LRU 刷脏基本都是干净的page
      ->buf_flush_do_batch(buf_pool, BUF_FLUSH_LIST, slot->n_pages_requested,page_cleaner->lsn_limit, &slot->n_flushed_list)
        slot->n_pages_requested 参数和io_cap相关。type为BUF_FLUSH_LIST
        ->buf_flush_start
          -> buf_pool->init_flush[flush_type] = TRUE
             设置对应的type正在进行刷新
          -> os_event_reset(buf_pool->no_flush[flush_type])
             设置没有被唤醒
        ->buf_flush_batch
          ->switch (flush_type)
            case BUF_FLUSH_LIST
            这里带入的flush list刷脏
            ->buf_do_flush_list_batch(buf_pool, min_n, lsn_limit)
              len = UT_LIST_GET_LEN(buf_pool->flush_list)
              获取flush list的长度
              ->for (buf_page_t *bpage = UT_LIST_GET_LAST(buf_pool->flush_list);count < min_n && bpage != nullptr 
                     && len > 0 &&bpage->get_oldest_lsn() < lsn_limit;bpage = buf_pool->flush_hp.get(), ++scanned)
                从尾部开始循环,结束条件是
                A count < min_n:如果刷新page数量的大于了需要刷新的page数量
                B len > 0:有需要刷新的page,空闲实例这个条件不会满足
                C bpage->get_oldest_lsn() < lsn_limit:lsn_limit一般比较大
                ->buf_flush_page_and_try_neighbors(bpage, BUF_FLUSH_LIST, min_n, &count)
                  这个参考代码已经标记了,但是LRU刷脏还会返回给free list
                  从刷脏后的异步IO的buf_page_io_complete函数中也可以看出BUF_FLUSH_LIST不需要淘汰LRU
                  ------------------------------------------------------BUF_FLUSH_LIST: don't evict
    
    1.1 buf_flush_LRU_list_batch
    buf_flush_LRU_list_batch
      ->for (bpage = UT_LIST_GET_LAST(buf_pool->LRU);
              bpage != nullptr && count + evict_count < max &&
              free_len < srv_LRU_scan_depth + withdraw_depth &&
              lru_len > BUF_LRU_MIN_LEN; 
              ++scanned, bpage = buf_pool->lru_hp.get())
       从LRU的尾部开始扫描page,实际上这里最重要的2个条件
       A: count + evict_count < max 
          如果刷新的page数量小于了预期srv_LRU_scan_depth参数指定深度的page数量
          这里evict_count代表stale page直接清理掉
          这里count代表刷脏后清理掉
       B: free_len < srv_LRU_scan_depth + withdraw_depth 
          如果当前instance中free的page数量小于了srv_LRU_scan_depth参数的设置
       注意如果这两个条件不满足则不会进行LRU刷脏
         ->if (bpage->was_stale())
           如果page是stale page
           ->buf_page_free_stale(----OK)
             ->auto *block_mutex = buf_page_get_mutex(bpage)
               上page buf_block_t的mutex   
             ->io_type = buf_page_get_io_fix(bpage)
               获取IO类型
             ->if (io_type == BUF_IO_NONE)
               如果io_type为NONE 这代表可以进行处理
               ->if (bpage->is_dirty())
                 如果是脏块
                 ->buf_flush_remove(bpage)
                   从flush list中删除,注意stale状态的page是直接从flush list删除的,而不需要刷盘
                   ->UT_LIST_REMOVE(buf_pool->flush_list, bpage)
                     从flush list中删除
                   ->bpage->set_clean()
                     set_oldest_lsn(0)
                   ->if (bpage->get_flush_observer() != nullptr)
                     -> bpage->get_flush_observer()->notify_remove(buf_pool, bpage);
                     -> bpage->reset_flush_observer();
                        和DDL 有关
                 <-buf_flush_remove
                 ->buf_LRU_free_page
                     从hash结构和LRU中去掉这个page,并且反还给free list
                     持有锁A.buf_pool->LRU_list_mutex B.buf_page_get_mutex(bpage)                
                   ->buf_page_can_relocate
                     if (!buf_page_can_relocate(bpage)) 
                     ->(buf_page_get_io_fix(bpage) == BUF_IO_NONE && bpage->buf_fix_count == 0)
                      判定是否处于IO fixed状态,如果处于则return false,不能直接去掉
                   <-buf_page_can_relocate
                   -> if (is_dirty)
                      如果是脏快则return (false),但是对这里来讲肯定不是前面已经判定了,如果是脏页这设置lsn为0
                   -> buf_LRU_block_remove_hashed
                      if (!buf_LRU_block_remove_hashed(bpage, zip, false))
                      从hash结构和LRU中去掉这个page,这需要持有 A.buf_pool->LRU_list_mutex B.buf_page_get_mutex(bpage) C.rw_lock_own(hash_lock, RW_LOCK_X)
                      同时需要确保IO没有被fixed buf_page_get_io_fix(bpage) == BUF_IO_NONE
                      ->buf_LRU_remove_block(bpage)
                        从LRU链表删除page
                        ->buf_LRU_adjust_hp(buf_pool, bpage);
                          调整风险指针
                        ->if (bpage == buf_pool->LRU_old)
                          如果来到3/8 端 需要对指针进行调整
                          ->buf_page_t *prev_bpage = UT_LIST_GET_PREV(LRU, bpage);
                            获取前一个page
                          ->buf_page_set_old(prev_bpage, TRUE); buf_pool->LRU_old_len++;
                            设置为old指针
                        ->UT_LIST_REMOVE(buf_pool->LRU, bpage)
                          从LRU 删除这个page
                        ->buf_page_is_old(bpage)
                          调整old len的长度
                        ->buf_LRU_old_adjust_len(buf_pool)
                          调整old page的指针
                      ->BUF_BLOCK_FILE_PAGE
                        ->buf_block_modify_clock_inc((buf_block_t *)bpage)
                          这一步主要作为优化,增加了buf_block_t的modify_clock++ 如果游标中的page本值没有改变则说明没有修改过,可以继续使用,否则?重新定位?
                      ->buf_page_hash_get_low
                        hashed_bpage = buf_page_hash_get_low(buf_pool, bpage->id);
                        上hash lock
                        ->HASH_SEARCH(hash, buf_pool->page_hash, page_id.fold(), buf_page_t *, bpage,
                                      ut_ad(bpage->in_page_hash && !bpage->in_zip_hash &&
                                      buf_page_in_file(bpage)),page_id == bpage->id);
                          进行hash查找,这里用到的是bpage->id这是一个space id和page no的结构体
                            /** Tablespace id. */space_id_t m_space;
                            /** Page number. */  page_no_t m_page_no;
                        ->返回找到的page,
                          A.如果找不到,报错 not found in the hash table B.当前page进行比对是否是同一个page,如果不是则报错 n hash table we find block  of  which is not
                      ->HASH_DELETE(buf_page_t, hash, buf_pool->page_hash, bpage->id.fold(), bpage)
                        从实例的hash结构删除这个page
                      -> switch (buf_page_get_state(bpage))
                         buf_page_set_state(bpage, BUF_BLOCK_REMOVE_HASH)
                         标记page为从hash中删除了,这是一个比较短暂的状态  ----注意page 状态的转换    
                   <- buf_LRU_block_remove_hashed 
                   ->btr_search_drop_page_hash_index((buf_block_t *)bpage)  
                     AHI 删除这个PAGE
                   ->buf_LRU_block_free_hashed_page((buf_block_t *)bpage) 
                     -> buf_block_set_state(block, BUF_BLOCK_MEMORY)       ----注意page 状态的转换  
                        先设置为BUF_BLOCK_MEMORY状态
                     ->buf_LRU_block_free_non_file_page
                       ->buf_block_set_state(block, BUF_BLOCK_NOT_USED)    ----注意page 状态的转换
                         设置page状态为BUF_BLOCK_NOT_USED
                       ->UT_LIST_ADD_FIRST(buf_pool->free, &block->page)
                         将page加入到free list
                   <-buf_LRU_block_free_hashed_page    
                 <-buf_LRU_free_page                
           <-buf_page_free_stale 
         ->else 如果不是stale page -------- 这里包含了是否为脏页,分别走不同的逻辑,如果不是脏页自己维护内存,如果是脏页异步IO维护内存
           ->mutex_enter_nowait         
             buf_block_t的mutex,这里拿这个锁,因为下面要进行判断      
           ->if (acquired && buf_flush_ready_for_replace(bpage))
             这个分支主要是走的是非脏页进行淘汰,不需要刷盘
             ->buf_flush_ready_for_replace(bpage)  主要判断是否为脏页,并且IO没有被fixed住,判定如下
               ->if (!buf_page_in_file(bpage))
               因为这里是扫描的LRU,如果有几种状态比如BUF_BLOCK_REMOVE_HASH/BUF_BLOCK_MEMORY/BUF_BLOCK_NOT_USED/BUF_BLOCK_READY_FOR_USE
               这几种状态的page是明显不应该在LRU上的,因此需要报错Buffer block " << bpage << " state " << bpage->state << " in the LRU list!";
               ->if (!buf_page_can_relocate(bpage))
                 需要判断page是否可以被重新填充,如果IO fixed了则不能重新填充
                 -> return(buf_page_get_io_fix(bpage) == BUF_IO_NONE && bpage->buf_fix_count == 0)
                   需要判断是否有IO FIXED的情况
               ->if (bpage->was_stale())
                 是否处于stale状态如果是则返回为true(判断space是否已经drop 过了),在这个流程下前面是判断过的
               ->bpage->is_dirty
                 返回的是 这个page是否是脏页,很明显如果是非脏页,这不需要刷盘     
             <-buf_flush_ready_for_replace
            ->如果不是脏页
              ->(buf_LRU_free_page(bpage, true))
                参考上面 从hash结构和LRU中去掉这个page,并且反还给free list,因为不是脏页,因此不需要刷盘
                ++evict_count,不需要刷盘的就是就是这个统计          
           ->else if (acquired && buf_flush_ready_for_flush(bpage, BUF_FLUSH_LRU))
             这个分支主要是走的脏页淘汰,这个需要刷盘的----------------------------- 刷盘后的异步IO线程来完成内存的维护
             ->buf_flush_ready_for_flush
               ->!bpage->is_dirty()||buf_page_get_io_fix_unlocked(bpage) != BUF_IO_NONE
                 主要判断是否io 被fix住了,并且判断是否是脏页
               ->(buf_page_get_state(bpage) != BUF_BLOCK_REMOVE_HASH)
             ->buf_flush_page_and_try_neighbors(bpage, BUF_FLUSH_LRU, max, &count)
               同样调用临近页刷脏,flush list也是这样刷的
               ->buf_flush_page
                 调用这里进行刷脏,见下1.2,从后面的IO 异步刷到磁盘的动作来看在buf_page_io_complete函数中,也是会进行LRU淘汰等管理操作,--------------------------------BUF_FLUSH_LRU: always evict
    
    1.2 buf_flush_page
    buf_flush_page
      刷脏
      先设置了page的IO fix等状态
      ->buf_flush_write_block_low
        ->buf_flush_init_for_writing
          初始化物理page的相关header 比较重要
        ->dblwr::write
          先看正常的刷脏逻辑
          ->page_id = bpage->id
          ->if (!sync && flush_type != BUF_FLUSH_SINGLE_PAGE)
            这里只有SINGLE_PAGE刷脏才会用到sync=true,正常的LRU和FLUSH LIST刷脏都走这里
            ->Double_write::submit(flush_type, bpage, e_block, e_len)
              ->dblwr = instance(flush_type, bpage)
                调用Double_write::instance,返回dblwr instance
                ->Double_write::instance buf0dblwr.cc:508
                  instance(flush_type, buf_pool_index(buf_pool_from_bpage(bpage)))
                  获取page所在buffer instance id,再次调用重载的Double_write::instance
                ->Double_write::instance buf0dblwr.cc:330
                  实际就是根据flush_type计算出使用哪一个dblwr instance
                  计算方式主要还是分LRU和FLUSH LIST ,其中BUF_FLUSH_LIST为后面8个
                  BUF_FLUSH_LRU为前面8个,主要还是根据buffer的instance来匹配用哪个
                  也就是每个instance使用2个BS,其中一个为BUF_FLUSH_LRU另一个为
                  BUF_FLUSH_LIST
              ->dblwr->enqueue(flush_type, bpage, e_block, e_len)
                Double_write::enqueue  
                ->Double_write::prepare
                  这个函数作用不大,主要检查一下page
                ->for (;;)
                 ->mutex_enter(&m_mutex)
                   对本dblwr instance加锁
                 ->if (m_buffer.append(frame, len))
                   调用dblwr::Buffer::append,这个函数如果正常写入则返回为true,跳出循环,否则buffer空间满
                   则返回flase
                   { break;}
                 ->if (flush_to_disk(flush_type))
                   buffer满刷入到磁盘,调用Double_write::flush_to_disk    
                   ->Double_write::wait_for_pending_batch   
                   ->Double_write::write_pages
                     ->segments->dequeue(batch_segment)
                       出队一个Batch_segment,出队算法?
    

    二、DBWRL 系统

    s_flush_list_batch_segments(buffer instance=8 innodb_doublewrite_pages=4 srv_n_write_io_threads=4,f0:  (4*9)*16K)  
    
           BS0                     BS1         ...          BS8       
    S0   S1   S2   S3      S0   S1   S2   S3         S0   S1   S2   S3
                   
    |     |    |    |      |    |    |    |          |    |    |    |
    |     |    |    |      |    |    |    |          |    |    |    |
    
    
    16K  16K 16K  16K     16K  16K 16K  16K   ...    16K  16K 16K  16K      f0:  (4*9)*16K 
            
            
     
                                                                
    s_LRU_batch_segments(buffer instance=8 innodb_doublewrite_pages=4 srv_n_write_io_threads=4,f1:(4*9)*16K)                                     
    s_single_segments(f1:512*16K)               
                   
           BS0                     BS1         ...          BS8                S37    S38  ...  S547   S548                    
    S0   S1   S2   S3      S0   S1   S2   S3         S0   S1   S2   S3          |      |         |      | 
                                                                                |      |         |      | 
    |     |    |    |      |    |    |    |          |    |    |    |           |      |         |      |
    |     |    |    |      |    |    |    |          |    |    |    |           |      |         |      |
                                                                                               
                                                                                           
    16K  16K 16K  16K     16K  16K 16K  16K   ...    16K  16K 16K  16K         16K    16K  ...  16K     16K                           
    
    
    ----------------------(4*9)*16K--------------------------------------      ----------512*16K---------
    
     
    s_segments flush:BS0...flush:BS8|LRU:BS0......LRU:BS8
    
    
    s_instances(vector):  
    
    Double_write0                 Double_write1          ...       Double_write14             Double_write15
          |                             |                                |                          |
    m_buffer(16K*4)               m_buffer(16K*4)                  m_buffer(16K*4)            m_buffer(16K*4)
    m_buf_pages(verctor(4))       m_buf_pages(verctor(4))          m_buf_pages(verctor(4))    m_buf_pages(verctor(4))
       --std::tuple                  --std::tuple                  --std::tuple               --std::tuple 
       --std::tuple                  --std::tuple                  --std::tuple               --std::tuple
       --std::tuple                  --std::tuple                  --std::tuple               --std::tuple
       --std::tuple                  --std::tuple                  --std::tuple               --std::tuple
    
    
    --------前8 instance为BUF_FLUSH_LIST使用----------             -----------后8个 instance为BUF_FLUSH_LRU---------
    
    2.1 DBLWR segment的建立
    dblwr::open
      ->Double_write::s_n_instances = std::max(4UL, srv_buf_pool_instances * 2)
        dblwr的实例个数和buffer pool的instance个数相关,并且每个buffer pool的instance
        包含了一个LRU list和一个flush list 两个dblwr实例,默认为16
      ->if (dblwr::n_files == 0){dblwr::n_files = 2}
        定义全局变量dblwr::n_files的个数和参数innodb_doublewrite_files有关,默认为2,一般也不会调整这个参数
        也就是dblwr物理文件的个数,从下面的判断来看最大数量不能大于Double_write::s_n_instances的定义
      ->if (dblwr::n_pages == 0){dblwr::n_pages = srv_n_write_io_threads}
        定义每一个batch segment的page数量,和参数innodb_doublewrite_pages有关,默认为4,也是srv_n_write_io_threads
        的大小
      这里使用默认参数innodb_doublewrite_files=2为列子   
      ->Double_write::s_files.resize(dblwr::n_files)
        定义s_files vecotr数组的大小为dblwr::n_files大小,也就是有多少的dblwr文件
      ->segments_per_file = (Double_write::s_n_instances / dblwr::n_files) + 1
        定义每个文件batch segment的个数,这里可以看到默认实际上就是16/2+1 也就是9个segments
        这里定义的batch segment
      ->dblwr::File::s_n_pages = dblwr::n_pages * segments_per_file
        定义每个文件用于batch segment的pages的总数,默认segments_per_file为9,dblwr::n_pages为4
        也就是36
      ->for (auto &file : Double_write::s_files)
        物理部分1
        循环每个dblwr物理文件,文件下标从0开始
        ->dblwr_file_open(dblwr::dir, &file - first, file, OS_DBLWR_FILE)
          建立文件,其中dblwr::dir为参数innodb_doublewrite_dir指定的位置
          其中可以看到建立文件的规则,比如文件名怎么来的
        ->pages_per_file = dblwr::n_pages * segments_per_file
          这里pages_per_file为每个文件包含的batch segment pages的个数
        ->if ((file.m_id & 1))
          如果文件的序号为奇数(0,1)
          ->pages_per_file += SYNC_PAGE_FLUSH_SLOTS / (Double_write::s_files.size() / 2)
            那么pages_per_file还需要加上segment 的page个数,默认为512(SYNC_PAGE_FLUSH_SLOTS)个pages用于single dblwr write
            而默认Double_write::s_files.size()就是2,那么默认的情况下dblwr文件1就会比文件0大512个page,这个512个用于
            single dblwr write
        ->Double_write::init_file(file, pages_per_file)
            根据计算的pages数量来初始化文件大小
          -----以上文件初始化完成,但是需要建立相应的内存结构
        ->Double_write::create_batch_segments(segments_per_file)
            这里输入的是每个文件batch segment的个数,但是初始化是LRU和FLUSH LIST都要初始化完成的
            ->n_segments = segments_per_file * s_files.size();
              用batch segment的个数*文件个数,默认为9*2=18
            ->n = std::max(ulint{2}, ut_2_power_up((n_segments + 1)))
              18+1 后取2的N次方,实际上就是32,这里可以看到内存结构实际上多了一些batch segment
            ->s_LRU_batch_segments = UT_NEW_NOKEY(Batch_segments(n))
            ->s_flush_list_batch_segments = UT_NEW_NOKEY(Batch_segments(n))
              初始化s_LRU_batch_segments和s_flush_list_batch_segments其大小都是32个batch segment
            ->total_pages = segments_per_file * dblwr::n_pages
              计算每个文件用于batch segment的page数量
            -> for (auto &file : s_files)
               循环每个dblwr文件
               ->for (uint32_t i = 0; i < total_pages; i += dblwr::n_pages, ++id)
                 这里以dblwr::n_pages也就是每个batch segment的pages个数作为增加值来初始化
                 每个batch segment
                 ->auto s = UT_NEW_NOKEY(Batch_segment(id, file, i, dblwr::n_pages))
                   建立batch segment,并且进行标号,从0开始标号
                 ->segments = (file.m_id & 1) ? s_LRU_batch_segments: s_flush_list_batch_segments;
                   如果dblwr文件为奇数这为s_LRU_batch_segments,偶数为s_flush_list_batch_segments
                   f0:s_flush_list_batch_segments
                   f1:s_LRU_batch_segments
                 ->success = segments->enqueue(s)
                   将初始化的batch segment放入到相应的队列中
                 ->s_segments.push_back(s)
                   并且放入s_segments数组中
        ->Double_write::create_single_segments(segments_per_file)
            物理部分2
            ->n_segments = std::max(ulint{2}, ut_2_power_up(SYNC_PAGE_FLUSH_SLOTS))
              因为SYNC_PAGE_FLUSH_SLOTS为512为2的n次方
            ->s_single_segments = UT_NEW_NOKEY(Segments(n_segments))
              分配对应数量的segment,作为single dblwr的segment
            ->n_pages = SYNC_PAGE_FLUSH_SLOTS / (s_files.size() / 2)
              默认s_files=2,因此这里n_pages=512
            ->for (auto &file : s_files) 
              开始循环每个文件
              ->if (!(file.m_id & 1) && s_files.size() > 1){continue;}
                偶数文件直接continue,因此这里是奇数文件 奇数文件就是s_LRU_batch_segments 对应的文件,那么这个文件要大一些
              ->start = dblwr::File::s_n_pages
                这里跳过batch segments部分
              ->for (uint32_t i = start; i < start + n_pages; ++i)
                这里start为batch segments部分,n_pages为single dblwr的segment的page数量,每次增加1
                ->auto s = UT_NEW_NOKEY(Segment(file, i, 1UL)); 
                  这里的1UL就代表一个page,根据信息初始化segment信息
                ->success = s_single_segments->enqueue(s)
                  加入到s_single_segments中这是一个Segments的数组
        ->Double_write::create_v2()  
          建立内存部分数据结构
          ->for (uint32_t i = 0; i < s_n_instances; ++i)
            根据s_n_instances的数量进行dblwr instance的初始化,s_n_instances就是buffer instance *2 为16
            ->ptr = UT_NEW_NOKEY(Double_write(i, dblwr::n_pages))
              其中dblwr::n_pages为batch segment对应的page数量,默认为4
              ->auto ptr = UT_NEW_NOKEY(Double_write(i, dblwr::n_pages))
                m_id(id), m_buffer(n_pages), m_buf_pages(n_pages)
                mutex_create(LATCH_ID_DBLWR, &m_mutex)
                这里主要初始化一个mutex和响应的buffer,初始化m_buffer和m_buf_pages
                A.m_buffer:定义为dblwr::Buffer类型,其本质为一段内存,其大小为n_pages+1,考虑到对齐操作因此+1,这是作为缓存用的
                B.m_buf_pages:定义为Double_write::Buf_pages类型,其为一个std::tuple<buf_page_t *, const file::Block *, uint32_t>
                  类型的vector,verctor的大小为n_pages定义,这是收集的脏数据的buf_page_t
             ->s_instances->push_back(ptr)
               将初始化好的Double_write的指针push到s_instances中
    
    2.2 DBLWR segment 的使用
    Double_write::flush_to_disk
      如果本instance的buffer满了则写入到磁盘
      ->wait_for_pending_batch()
        等待其他batch write 写入
      -> segments = flush_type == BUF_FLUSH_LRU ? s_LRU_batch_segments : s_flush_list_batch_segments;
         根据类型分配到底是那个BS,因为一个instance分为2个BS,一个是LRU的一个是FLUSH的
      -> segments->dequeue(batch_segment)
         出队已满的BS,因为一个BS,就是4个page的物理空间。这里需要确认如何选择BS的,debug来看都是整数
         !!!!!在某些情况下不一定buffer满才刷page,因此可能存在写 1 2 3 个page的情况
      -> batch_segment->start(this)
         调用Batch_segment::start,传入本instance,标记已经开始刷盘
      -> batch_segment->write(m_buffer)
         调用Batch_segment::write,将instance中buffer的数据写入到本batch_segment
         ->Segment::write(buffer.begin(), buffer.size())
           ->IORequest req(IORequest::WRITE | IORequest::DO_NOT_WAKE)
             建立io请求
           ->req.dblwr()
             m_type |= DBLWR
             设置type为DBLWR
           ->os_file_write_retry(req, m_file.m_name.c_str(), m_file.m_pfs,ptr, m_start, len)
             len 也可以是一个page,force的情况,也可以是4个page或者1个page,
             ->pfs_os_file_write_func
              ->os_file_write_retry
               ->os_file_pwrite(同步IO)
      -> m_buffer.clear()
         清理buffer instance的缓存
      ->if (is_fsync_required())
        调用Double_write::is_fsync_required    
        ->srv_unix_file_flush_method != SRV_UNIX_O_DIRECT && srv_unix_file_flush_method != SRV_UNIX_O_DIRECT_NO_FSYNC
         是否需要刷盘,如果为O_DIRECT 就不需要刷DBLWR,如果需要刷盘则调用下面的
        -> batch_segment->flush()
           Segment::flush  
           ->os_file_flush(m_file.m_pfs)
      ->for (uint32_t i = 0; i < m_buf_pages.size(); ++i)
        开启循环           
        ->bpage = std::get<0>(m_buf_pages.m_pages[i])
        ->bpage->set_dblwr_batch_id(batch_segment->id())
        ->write_to_datafile(bpage, false, std::get<1>(m_buf_pages.m_pages[i]),std::get<2>(m_buf_pages.m_pages[i]))
          脏数据写入到数据文件,page\false\压缩相关\长度
          Double_write::write_to_datafile
          这里不考虑压缩的page也就是e_block == nullptr的情况
          ->Double_write::prepare(in_bpage, &frame, &len)
            做一些检查
          ->IORequest io_request(type)
            建立IO请求
            IORequest::WRITE 
            不需要做fsync
            IORequest::DO_NOT_WAKE
          ->fil_io(io_request, sync, bpage->id, bpage->size, 0, len, frame, bpage)
            ->shard = fil_system->shard_by_id(page_id.space())
              获取shard
            ->shard->do_io((type, sync, page_id, page_size, byte_offset, len, buf,message)
              Fil_shard::do_io 
    

    三、异步IO部分

    预读为例子:
     -----------共享io_context_t-------------- 
      |                                    |
      |                                    |
    session thread   kernel             AIO thread             
      |                |                   |    
      |                |                   |
      |                |                   |
      |--------->      |                   |
      |  提交IO        |                   |
      | IO_SUBMIT    处理IO请求            |
      |                | <-----------------|                 
     提交后session            io_getevents |
     就可以继续了不           收割IO       |
     等待IO完成                            |
                                        Fil_shard::complete_io
                                        处理文件的相关信息
                                        buf_page_io_complete
                                        处理page的信息
    异步IO主要完成不是马上需要page的情况,可以将page的读取
    由AIO thread 处理后直接调入到内存,比如预读。
    
    3.1 刷脏提交IO
    Fil_shard::do_io
     ->aio_mode = get_AIO_mode(req_type, sync)
       获取aio的类型,这里一共有几种IO类型,
       AIO_mode::SYNC   同步 IO
       AIO_mode::LOG    redo IO
       AIO_mode::NORMAL 普通 异步IO
       AIO_mode::IBUF   ibuf IO
       if (sync) { //如果是sync就是同步IO
        return AIO_mode::SYNC;
      } else if (req_type.is_log()) {
        return AIO_mode::LOG;
      } else {
        return AIO_mode::NORMAL;
      }
     ->并且按照读写分别放入
       srv_stats.data_read.add
       srv_stats.data_written.add
       中
     ->bpage = static_cast<buf_page_t *>(message)
     ->slot = mutex_acquire_and_get_space(page_id.space(), space)
       为打开的文件分配slot,注意这里还包含了关闭打开的文件,如果
       超过了innodb_open_file_limits 参数
       ->fil_system->m_max_n_open <= s_n_open
       ->fil_system->close_file_in_all_LRU
     ->opened = prepare_file_for_io(file, false)
       Fil_shard::prepare_file_for_io
       ->if (!open_file(file, extend))
         Fil_shard::open_file
         打开文件
     ->os_aio(os_aio_func)
       这个函数是同步IO和异步IO都是它调入主要看AIO_mode
       -> if (aio_mode == AIO_mode::SYNC && !srv_use_native_aio)
         -> if (type.is_read())
           调用pread,直接读取
          { return (os_file_read_func(type, name, file.m_file, buf, offset, n)) }
           ->os_file_read_page
             ->os_file_pread
                 ++os_n_file_reads
                 os_n_pending_reads.fetch_add(1)
               ->os_file_io
                 os_n_pending_reads.fetch_sub(1)
         -> 调用pwrite,直接写入 
          return (os_file_write_func(type, name, file.m_file, buf, offset, n))
          和上面差不多的路径           
       如果不是同步IO就需要进行异步IO,将请分配给异步IO线程
       -> array = AIO::select_slot_array(type, read_only, aio_mode)
       -> slot = array->reserve_slot(type, m1, m2, file, name, buf, offset, n, e_block)
       -> if (type.is_read())
          -> if (srv_use_native_aio){
             ++os_n_file_reads
          -> array->linux_dispatch(slot)
       -> if (type.is_write())
          -> if (srv_use_native_aio){
             ++os_n_file_writes
          -> array->linux_dispatch(slot)
            -> 这里调用io_submit进行IO 提交,随后由异步IO线程进行收集
       -> return (DB_SUCCESS)
          /* AIO request was queued successfully! */
     ->if (sync) {
       如果是同步IO ,注意这里是同步IO,不是fsync
       ->complete_io(file, req_type)
         Fil_shard::complete_io
         ->--file->n_pending;
          pending IO 减1
          ->if (type.is_write())
    
    3.2 异步IO线程处理
    fil_aio_wait
      ->os_aio_handler
       ->os_aio_linux_handler
         ->handler.poll
           主要看做IO的收集的部分,因为IO正常是用户线程自己提交的
           ->slot = find_completed_slot(&n_pending);  //这里看应该是IO比较慢的情况
             找到可能的partial IO slot,通过循环整个segment中的solt完成
             ->LinuxAIOHandler::find_completed_slot
               ->for (ulint i = 0; i < m_n_slots; ++i, ++slot)
                 -> if(slot->is_reserved)
                    循环整个segment中的slot情况,如果存在,++*n_pending
                    增加pending IO
                   ->if (slot->io_already_done) 
                    如果IO已经完成
                   ->return (slot)
                    返回这个slot
           ->if (slot != nullptr)
             如果slot不为null,这检查是否是否为partial IO slot
             如果是需要重新提交
           ->err = check_state(slot);
           ->如果err为DB_FAIL,这调用resubmit来提交IO
           ->else srv_set_io_thread_op_info(m_global_segment,
                                    "waiting for completed aio requests");
             这里可以看出正在等待完成的aio 请求,如果有则负责收割IO
           ->collect()
              LinuxAIOHandler::collect
             ->io_getevents()    
               异步IO堵塞收集,这里收割IO 
      ->srv_set_io_thread_op_info(segment, "complete io for file"); ---
        设置IO已经完成
      ->Fil_shard::complete_io
        这个函数不管同步IO还是异步IO都要调用,这个函数主要是对文件信息的维护
        是否
        ->--file->n_pending; 
         pending IO 减少
        ->if (type.is_write())
          如果是写操作,则调用
          ->write_completed
           ->add_to_unflushed_list(file->space)
             ->UT_LIST_ADD_FIRST(m_unflushed_spaces, space);
               这个list主要用于存储需要flush的数据文件,以便后面使用
          ->UT_LIST_ADD_FIRST(m_LRU, file)
            如果是读操作直接加入 file 的 LRU链表的头部,如果淘汰file,这按照这个淘汰。   
      ->switch (file->space->purpose)
        如果是普通的page则调用
        ->srv_set_io_thread_op_info(segment, "complete io for buf page"); ---
        ->buf_page_io_complete(static_cast<buf_page_t *>(m2), false);
          这个函数不管同步IO还是异步IO都要调用
          ->switch (io_type)
           ->case BUF_IO_READ
             ->buf_page_set_io_fix(bpage, BUF_IO_NONE);
             ->buf_pool->n_pend_reads.fetch_sub(1);
             ->buf_pool->stat.n_pages_read.fetch_add(1);
           ->case BUF_IO_WRITE
             ->buf_flush_write_complete(bpage)
               这个过程加buffer pool instance flush_state_mutex 锁
               还完成dblwr的解锁
               ->buf_pool = buf_pool_from_bpage(bpage)
               ->buf_flush_remove(bpage);
                 从flush中删除,同上
               ->buf_page_set_io_fix(bpage, BUF_IO_NONE);
                 清理IO状态
               ->flush_state_mutex 解锁
               ->dblwr::write_complete(bpage, flush_type)
                 ->Double_write::write_complete
                   释放batch segment等资源
                   ->fil_flush_file_spaces(FIL_TYPE_TABLESPACE)
                   负责对unflush的文件进行刷盘
                    ->fil_system->flush_file_spaces(purpose) Fil_system::flush_file_spaces
                      ->for (auto shard : m_shards)
                        循环每个file shard
                        ->shard->flush_file_spaces(purpose)
                          ->for (auto space = UT_LIST_GET_FIRST(m_unflushed_spaces); space != nullptr;space = UT_LIST_GET_NEXT(unflushed_spaces, space))
                            循环每个unflushed datafile的文件和purpose进行比对,如果目标相同则
                            space_ids.push_back(space->id)
                            space id放入到space_ids vector中
                          ->循环space_ids 这里已经是收集的做了刷盘的文件
                            ->做 space_flush(space_id)
                              异步IO合并了刷盘的IO 请求,进行fsync的压力也会变小  
                              -> ++file.n_pending_flushes
                                 增加pending IO,这个IO来自数据文件的刷盘
                              -> os_file_flush(file.handle)
                                -> os_file_fsync_posix(file)
                              ->--file.n_pending_flushes
                                 减少pending IO                                      
               ->if (flush_type == BUF_FLUSH_LRU)
                 ->evict = true
                   需要从LRU中剔除page
                 ->if (evict && buf_LRU_free_page(bpage, true))
                   同上,从hash结构和LRU中去掉这个page,并且反还给free list
    

    相关文章

      网友评论

          本文标题:MySQL:刷脏相关部分

          本文链接:https://www.haomeiwen.com/subject/lefrwdtx.html