技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> MySQL --> MySQL数据库InnoDB存储引擎 Buffer pool LRU List Flush策略详解

MySQL数据库InnoDB存储引擎 Buffer pool LRU List Flush策略详解

浏览:3840次  出处信息

buffer pool LRU list flush(innodb_buffer_pool_size)

每当一个新页面读取buf pool之后,MySQL数据库InnoDB存储引擎都会判断当前buf pool的free page是否足够,若不足,则尝试flush LRU链表。

 

在MySQL 5.6.2版本中,MySQL数据库InnoDB存储引擎引入了page_cleaner线程,将遍历LRU链表,flush dirty page,释放free page的重任,从用户线程中剥离出来。page cleaner线程1s运行一次。

 

本小节的以下部分,将分别对MySQL 5.6.2以前的LRU list flush策略,以及page cleaner线程的LRU list flush策略,做详细的分析。

 

before MySQL 5.6.2

在MySQL 5.6.2之前,用户线程在读入一个page (buf_read_page)、新建一个page(buf_page_create)、预读page(buf_read_ahead_linear) 等等操作时,都会在操作成功之后,调用buf_flush_free_margin函数,判断当前buf pool是否有足够的free pages,若free pages不足,则进行LRU list flush,释放出足够的free pages,保证系统的可用性。接下来我们可以看看buf_flush_free_margin函数究竟是如何处理的?

Buf0flu.c::buf_flush_free_margin()

// 判断当前buf pool中需要flush多少dirty pages,才能够预留出足够的可被替换的

// 页面(free pages or clean pages in LRU list tail)。可用pages由以下两部分组成:

// 1. buf pool free list中的所有page,都是可以立即使用的。

// 2. buf pool LRU list尾部(5+2*BUF_READ_AHEAD_AREA)所有的clean pages。

//  其中:BUF_READ_AHEAD_AREA为64,是一个linear read ahead读取的大小,1 extent

//

// 那么多少可用的pages才是足够的呢?InnoDB中的定义如下:

// BUF_FLUSH_FREE_BLOCK_MARGIN() + BUF_FLUSH_EXTRA_MARGIN()

// 1. BUF_FLUSH_FREE_BLOCK_MARGIN = 5 + BUF_READ_AHEAD_AREA = 5 + 64 = 69

// 2. BUF_FLUSH_EXTRA_MARGIN = ((BUF_FLUSH_FREE_BLOCK_MARGIN / 4 + 100) /

//                                                                      srv_buf_pool_instance)

//  extra_margin的取值,为127除以系统中buf pool的个数

// 因此,对于1个instance的InnoDB系统,可用的个数约为:69 + 127 = 196

//

n_to_flush = buf_flush_LRU_recommendation();

// 读取buf pool free list中的页面个数

n_replaceable = UT_LIST_GET_LEN(buf_pool->free);

// 从buf pool LRU list tail开始向前遍历BUF_LRU_FREE_SEARCH_LEN(5+2*64)个

// pages,若page可以被replace,则增加n_replaceable取值

// LRU中的page,可以replace的条件,可见前面申请新空闲页中的分析。简单

// 来说,page一定是clean的;而且page没有被其他线程pin住,就可以replace

if (buf_flush_ready_for_replace())

n_replaceable++;

// 计算出n_replaceable取值之后,InnoDB还做了一个小小的优化

// 由于此时的flush LRU list是在用户线程中直接进行的,会影响到用户的响应时间

// 因此做了一个判断:若n_replaceable大于BUF_FLUSH_FREE_BLOCK_MARGIN

// 那么就不需要开始一次LRU flush;否则,返回需要flush的LRU list中的pages

// 个数为n_to_flushed

if (n_replaceable >= BUF_FLUSH_FREE_BLOCK_MARGIN)

return 0;

n_to_flush = BUF_FLUSH_FREE_BLOCK_MARGIN + BUF_FLUSH_EXTRA_MARGIN

- n_replaceable;

retrun n_to_flush;

// 若函数buf_flush_LRU_recommendation返回值大于0,说明需要进行LRU list flush

// 则调用buf_flush_LRU函数,进行LRU list flush。

// 由于buf_flush_LRU在MySQL 5.6.2之前与之后的版本中并无太大区别,因此将在

// 后面集中给于分析。

if (n_to_flush > 0)

buf_flush_LRU();

 

由于buf_flush_free_margin函数是在用户线程中调用执行的,若需要flush LRU list,那么对于用户的响应时间有较大的影响。因此,在MySQL 5.6.2之后,InnoDB专门开辟了一个page cleaner线程,处理dirty page的flush动作(包括LRU list flush与flush list flush),降低page flush对于用户的影响。在接下来的一小节,我们来看看page cleaner线程是如何处理每次的LRU list flush的。

after MySQL 5.6.2

在MySQL 5.6.2之前,buf pool LRU list的flush,是在用户线程中完成的,会影响用户的响应时间,为了解决此问题,MySQL 5.6.2版本,MySQL数据库InnoDB存储引擎在后台新增了一个page cleaner线程,专门用于处理dirty page的flush (包括LRU list与flush list)。接下来我们来看看page cleaner线程是如何处理的?

srv0start.cc::innobase_start_or_create_for_mysql();

// 在InnoDB启动的主流程中,创建page cleaner线程

os_thread_create(buf_flush_page_cleaner_thread, NULL, NULL);

while

// 如果当前系统处于活跃状态,则page cleaner线程每次休眠1s

if (srv_check_activity(last_activity))

page_cleaner_sleep_if_needed();

next_loop_time = ut_time_ms() + 1000;

// flush LRU list,在MySQL 5.6.6中,LRU list flush,只能由page cleaner线程发起

// 1. InnoDB新增了一个系统参数,innodb_lru_scan_depth,用于控制每次LRU lsit

//      flush需要扫描的LRU tail链表的长度,默认取值为1024

// 2. 于此同时,一个LRU list flush操作,被拆分为每次100个chunk大小的操作

//      保证能够及早的释放出free page,用户线程不需要做长时间的等待。

page_cleaner_flush_LRU_tail();

for (j = 0; j < srv_LRU_scan_depth; j += PAGE_CLEANER_LRU_BATCH_CHUNK_SIZE)

buf_flush_LRU(PAGE_CLEANER_LRU_BATCH_CHUNK_SIZE);

// 进行完LRU list的flush之后,再进行flush list的flush,将在以下的章节分析

page_cleaner_flush_pages_if_needed();

 

buf_flush_LRU

在MySQL 5.6.2前后的版本中,LRU list flush的不同之处在于是由用户线程发起,还是有后台page cleaner线程发起。但是,无论是用户线程,还是后台page cleaner线程,再决定需要进行LRU list flush之后,都会调用buf_flush_LRU函数进行真正的flush操作。

 

不同之处在于,MySQL 5.6.2之前,用户线程调用的buf_flush_free_margin函数,在判断是否真正需要进行LRU list flush时,将LRU list tail部分的clean pages也归为可以被replace的pages,不需要flush。而在page cleaner线程中,每隔1s,无论如何都会进行一次LRU list flush调用,无论LRU list tail中的page是否clean。这也可以理解,用户线程,需要尽量降低flush的概率,提高用户响应;而后台线程,尽量进行flush尝试,释放足够的free pages,保证用户线程不会堵塞。

 

接下来详细分析下buf_flush_LRU函数是如何实现的 (主要基于MySQL 5.6.6 labs版本的实现,对比了MySQL 5.5.16版本,有部分差别)?

 

  1. 初始化BUF_FLUSH_LRU类型的flush (buf_flush_start()).

 

  1. 开始进行BUF_FLUSH_LRU类型的flush (buf_flush_batch). 首先获得buf pool的mutex,在整个LRU list flush过程中,buf pool mutex一直持有。因此flush过程中,用户线程是无法分配新页面的,这也是为什么在前面,LRU list flush要被划分为一个个小chunk的原因所在。

 

  1. 首先尝试从buf pool的unzip LRU中释放free pages (MySQL 5.6.6版本新增)。若最终释放的空间不足,则继续调用buf_flush_LRU_list_batch函数,flush common LRU list。

 

  1. 从LRU list尾部向前遍历,对于读取到的page,判断page是否属于clean page并且未被pin住,若是,则直接调用buf_LRU_free_block函数释放当前page,从LRU链表中删除,插入free链表。

 

  1. 若page不是clean page,则调用buf_flush_page_and_try_neighbors函数flush当前page (与前面Buffer Pool页面分配章节不同,在那个章节,只flush一个page,调用的函数是buf_flush_page)。

 

后台LRU flush,在flush当前页面的同时,也会尝试对当前页面的neighbors进行flush (neighbors的定义为:当前page所属的read ahead area属于同一neighbors,64个pages,一个extent)。遍历当前页面所属的extent,尝试对其中的每一个page进行flush。在LRU list flush中,可以被flush的neighbor page必须满足两个条件:

l  neighbor page必须是old page (bpage->old);疑问:bpage->old何时被设置?

l  neighbor page必须未被pin在内存中,未被使用,当前LRU flush线程能够获取page上的latch,而不需要等待。

 

满足以上两个条件的neighbor page,就可以调用buf_flush_page函数进行真正的flush了。关于此函数的处理流程,可见本文上面的分析。

 

  1. 在完成LRU list flush之后(buf_do_LRU_batch函数返回),释放buf pool mutex,然后调用buf_dblwr_flush_buffered_writes函数(在MySQL 5.5.16版本中,这个函数是buf_flush_buffered_writes)将DoubleWrite buffer flush到磁盘上,然后将DoubleWrite buffer对应的dirty page写出到正确的位置(sync)。sync的过程中,会发出signal通知write thread,write thread收到写成功的消息之后,会调用buf_page_io_complete函数,完成dirty page flush的最后清理操作(本文前面已经提到,需要清理的包括从flush list中删除,清除page的oldest_modification标识等等)。

 

  1. 最后,与步骤1对应,将步骤1初始化的BUF_FLUSH_LRU标识清理(buf_flush_end),然后记录相应的统计信息(buf_flush_common)。完成这些操作之后,一次buf_flush_LRU操作结束。

建议继续学习:

  1. Buffer和cache的区别是什么?    (阅读:6849)
  2. Linux操作系统中内存buffer和cache的区别    (阅读:5328)
  3. 快速预热Innodb Buffer Pool的方法    (阅读:4036)
  4. MySQL数据库InnoDB存储引擎 Insert Buffer实现机制详解    (阅读:3597)
  5. InnoDB之Dirty Page、Redo log    (阅读:3490)
  6. grep 命令的buffer选项    (阅读:3113)
  7. 小心grep 的buffer    (阅读:3068)
  8. ConcurrentHaspLRUHashMap实现初探    (阅读:2826)
  9. HBase如何合理设置客户端Write Buffer    (阅读:2742)
  10. 浅谈数据库系统中的cache    (阅读:2477)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1