MySQL:刷脏相关部分

2023-11-12  本文已影响0人  重庆八怪

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

一、刷脏

当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
上一篇下一篇

猜你喜欢

热点阅读