MySQL源码学习(二) Buffer Pool
MySQL源码学习(二) Buffer Pool
Buffer Pool是innodb的核心组件之一,所有数据的读取,都会先放到Buffer Pool中,再读取出来,而不是直接读取datafile文件。那么对于类似笔者这样的新手来说,从哪里开始阅读buffer pool的代码呢?在源代码中,几乎搜不到”buffer pool”这样的关键字?
1. 从buf_pool_t开始
在mysql代码库的src/storage/innobase/include目录下,大家可以找到buf0buf.h头文件,这里就是存放buffer pool核心管理结构的地方。在头文件中, struct buf_pool_t这个定义,就是这个关键结构体。大家可以其中看到一个一个熟悉关键字:”LRU”,没错,大名鼎鼎的LRU链表就在这其中,但还有其他很多的字段,在这里不一一展开,后面会通过与LRU链表最相关的几个链表逐一展开。
2. 6大链表
图1 Buffer Pool 6大链表
如图1所示,buf_pool_t结构体中的6个关键字段: free, LRU, unzip_LRU, flush_list, zip_clean, zip_free分别对应了6大链表。其中:
l free
它是在系统初始化阶段bufpool进行初始化后唯一显式调用链表初始化函数进行init操作的唯一bufpool链表。系统内的第一个LRU链表块,必然是从free链表中获取到的,当flush模块脏页刷新完成,LRU链表节点就会被清除或者移动到LRU链表结尾等待清除,清除LRU之后的节点仍旧是回归到free链表内。
l LRU
当有LRU链表为空时,必然从free链表获取空闲节点,并进行异步IO读将页读入bufpool,并加入LRU链表,LRU链表长度过大的情况下,会进行尾部刷新,刷新失败会进行更彻底的直接通过LRU进行脏页刷新(BUF_FLUSH_LRU方式),flush链表节点得到释放脏页完成刷新,并同时把LRU链表的脏块也完成移除
l Unzip_LRU
本链表实际是LRU链表的一个子集,在压缩页控制块(buf_page_struct)中的压缩页需要进行解压缩以进行各种记录级读写操作时,该链表将发挥作用,因此可以说,插入到了unzip_LRU链表就一定页在LRU链表中,反之则未必。
l Flush_list
bufpool最重要的三个链表之二,实际上也是LRU链表的子集,所以读入bufpool的页都通过压缩的或者非压缩的控制块进行管理,最初一定是在LRU链表中,当事务部分(和mini事务部分)完成commit操作时候,实际上就意味着内存写的成功,脏页必然要加入flush链表,并等待异步IO线程(系统主线程子系统组成之一)进行刷新操作(linux原生异步IO和作者Heikki Tuuri自己用条件变量实现的模拟异步IO,buf_pool_struct提到过,存储部分会做详细说明)动作。
l zip_clean
本链表以目前的源代码来看仅用于调试功能。
l zip_free
bufpool6大链表中最特殊的一个,链表的根节点可以看做“是一个指针数组”,伙伴系统的精髓就在于按照2的倍数进行紧邻内存块的合并和拆分,进而达到高效管理、代码复杂度低的效果。这个指针数组按照块大小实际包含4层,1024,2048,4096和8192,每一层基结点只管理同类大小的块。
从图我们可以看出,LRU是整个Buffer Pool的核心,因此我们以LRU为线索展开说明,zip_clean和zip_free较为特殊,本文暂不做详细说明,
3. LRU分配
从LRU和free的说明可以知道,LRU都是从free链表中获得真正的内存空间的。分配的函数入口就在:storage/innobase/buf目录下的buf0lrn.cc文件中的 buf_LRU_get_free_block函数
图2 buf_LRU_get_free_block主要流程
图2流程中标志了分配的整个流程,但并不是所有情况下,都会完全执行这个流程,例如:当free链表仍未被使用完时,执行完buf_LRU_get_free_only后就会返回。如果free链表已被用完,或者buf_LRU_get_free_only返回为NULL,则会执行buf_LRU_scan_and_free_block,从已有的LRU列表中找到一个block然后释放。buf_LRU_scan_and_free_block会在第4小节中进行阐述
l buf_LRU_get_free_only
返回一个空闲的block,这个block来自于free链表,如果free链表为空(内存耗尽),则返回NULL
图3 buf_LRU_get_free_only函数主体
从图3中我们可以看到,首先会从free链表的头部开始便利,找到一个可用的block,并把block的状态设置为BUF_BLOCK_READY_FOR_USE的状态,然后返回。
图4 若无法从free链表中获取到block,则先释放一个LRU钟的block
这里不得不提一下buf_LRU_get_free_block中的这段代码,如果是第一次进行扫描,则会从LRU的尾部开始扫描(LRU的特点,这里可以提升效率),但是如果不是第一次扫描,则会扫描整个LRU。但是如果扫描了一次,还是没找到呢?看下图
图5 强制刷新一个page,并将page放到free链表中
如果扫描超过1次没有找到能释放的LRU,则会让thread sleep一段时间,等待page clean刷一些数据到磁盘上,然后会强制执行一次刷新,将1个page刷到磁盘上,然后再放到free链表中。这里要注意,LRU释放掉一个page后,并不会直接使用这个page,而是将它先放回free链表,然后再按正常流程拿过来。
4. LRU释放
图6 LRU释放流程
LRU的释放,可以看到LRU的释放,会从unzip_LRU链表和common_LRU链表中进行释放。unzip_LRU我们上面已经介绍,那么common_LRU是什么呢?其实就是LRU本身。另外从这里也可以看出,unzip_LRU就是LRU的一个子集,释放unzip_LRU就是释放LRU本身。
我们再进入buf_LRU_free_from_common_LRU_list函数,我们可以看到:
图7 buf_LRU_free_from_common_LRU_list函数主体
所谓释放LRU,是必须要保证这个page是clean的,如果page不是clean的,就需要刷脏。这也是为什么在分配过程中,有可能出现找不到能释放的page的原因。
更深入的释放流程涉及到buffer pool的内存管理,我们会在下次再进行深入探讨
- 点赞
- 收藏
- 关注作者
评论(0)