原创mysql内核源代码深度解析 缓冲池 buffer pool 整体概述

Posted 五千岁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原创mysql内核源代码深度解析 缓冲池 buffer pool 整体概述相关的知识,希望对你有一定的参考价值。

老刘原创文章,CSDN首发!转载请注明出处。


mysql的内存管理庞大而先进,这在mem0pool.c文件的开头注释中都有说明,粗略的可以分成四部分,包含9大块:

buffer pool,

parsed andoptimized SQL statements,

data dictionarycache,

log buffer,

locks for eachtransaction,

hash table forthe adaptive index,

state andbuffers for each SQL query currently being executed,

session foreach user, and

stack for eachOS thread.

9大块通过4部分进行管理

A solution tothe memory management:

 

1. the bufferpool size is set separately;

2. log buffersize is set separately;

3. the commonpool size for all the other entries, except 8, is set separately.

也就是缓冲池,redo日志缓冲,普通池和8(用户session信息,可看做一部分)

redo日志缓冲由redo部分单独管理,bufferpool也就是缓冲池是一个复杂的部分,内容很多,普通池上面说了,除了8,和1,2.其余的都归它管。上面这个结构就是mysql内存子系统的完整图景。如图所示:


本篇从缓冲池(buf pool)讲起,然后会讲解common pool和插入缓存。


在做完整、全面而详细的缓冲池构架分析之前,必须先要穷举所涉及到的全部bufpool子系统组件和重点,内容非常之多。

(1)Buf pool五大组成模块

首先,bufpool子系统可以分成最基本的五个模块组件。


缓冲池例程常规管理(buffer pool routines),进行buf pool(多)实例管理,协调另外四模块进行运行,对存储、事务、redo日志、系统主线程等外部子系统等提供调用函数接口,是五个模块的核心。

LRU链表管理(LRU replacement algorithm),buf pool中基础控制存储单元(下文即将详细介绍)如buf_block_struct和buf_page_struct,都需要在对应的非压缩与压缩LRU链表中进行管理,从设计思想上需要实现加入LRU链表节点、删除LRU链表节点两个基本点,进而需要实现分配块、释放块两个高级功能,由此衍生出15个可以被外部模块调用的重要主函数;另外,虽然不为外部接口调用,但与主函数关联的仍旧有14个重要辅助函数(主函数的全部实现与嵌套细节),这两部分相当于LRU全部43个函数中的60%以上。

刷新机制(flush algorithm),bufpool部分与底层IO交互最紧密的模块,直接完成刷新动作,提供的各种刷新接口分别被子系统之内的所有模块调用。

刷新方式可以分为:通过LRU链表刷新的方式(BUF_FLUSH_LRU)和通过flush链表刷新的方式(BUF_FLUSH_LIST)。从功能来说,flush模块也必然包含flush链表的管理功能,实现链表节点的插入,删除,进而实现单块刷新、临近页刷新、doublewrite组件刷新这三个主要功能,并实现外部接口,供事务部分(和mini事务部分)commit动作中实现flush链表脏块的插入。

伙伴系统(Binary buddy allocator for compressed pages),在bufpool里面,伙伴系统并不是实现统一内存分配管理的单元,它的作用仅仅限于分配buf控制块(buf_page_struct)所需压缩页内存分配动作,但仍具有不可小视的巨大作用。Bufpool最终的目标是为实现各种供文件存储系统、事务管理、主进程调度单元等使用的外部接口,而这些部分基本都需要或多或少的对buf控制块中所管理的页中的数据记录(buf_block_struct->buf_page_struct.zip.data)进行读写操作,在伙伴管理分配内存之后,底层还要根据系统运行需要,进行压缩页到非压缩页的转换,可以说是极端重要的一个组件。

读缓存(buffer read),实现的功能简单直接,单页异步读、随机读、线性预读,运行这些功能的同时也存在着根据不同情况,进行的LRU链表(LRU和unzip_LRU)对读取到的页的控制块(buf_block_struct或buf_page_struct)进行节点插入、删除、置于链表末尾等操作,有LRU操作的地方自然也可能存在flush链表的刷新动作。

(2)四个重要结构体

其次,在子系统五大组件运行的过程中,涉及到四个重要的结构体,


缓冲池控制实例(buf_pool_struct),实现bufpool实例管理的核心数据结构,包含上文提到下文会详细描述的6大链表表头(UT_LIST_BASE_NODE_T),包含异步IO刷新条件变量(os_event_t)、LRU和flush链表互斥体、压缩与非压缩页hash表,上面这些仅仅是最重要部分。

struct buf_pool_struct{

。。。。。。

       ulint             LRU_old_ratio;

       mutex_t              LRU_list_mutex;

       rw_lock_t    page_hash_latch;

       mutex_t              free_list_mutex;

。。。。。。

       hash_table_t*     page_hash;

。。。。。。

       UT_LIST_BASE_NODE_T(buf_page_t)flush_list;

。。。。。。

       UT_LIST_BASE_NODE_T(buf_page_t)free;

。。。。。。

       UT_LIST_BASE_NODE_T(buf_page_t)LRU;

。。。。。。

};//结构体的内容难以一时尽述,下文会在每个模块中需要用到的部分时逐步引用并逐一加以解释。

底层内存分配单元(buf_chunk_struct),在无数的5.5以上版本mysql内核分析文章中,这个结构体都是最容易被人忽视的部分,但从某个意义上说是极其重要的,因为它最贴近bufpool的底层—OS内存分配,所有的buf控制块(buf_block_struct),都是挂载到6大链表中并可以直接通过的(buf_pool_struct)进行管理,但是这些结构体最初的内存分配动作,都是在(buf_chunk_struct)结构体的初始化阶段完成的,(buf_chunk_struct)又是(buf_pool_struct)的最基础最底层的内存分配单元。虽然这部分在代码量级上可说是“无足轻重”,但就重要性来讲绝对不能无视。

struct buf_chunk_struct{

       ulint             mem_size;    /*!< allocated size of the chunk */

       ulint             size;              /*!< size of frames[] and blocks[] */

       void*            mem;            /*!<pointer to the memory area which

                                   wasallocated for the frames */

       buf_block_t*      blocks;         /*!<array of buffer control blocks */

};//chunk结构体内容较少,此处已经列举全部内容,下文会根据模块引用做说明。

非压缩页控制块(buf_block_struct),在网易老姜所著的《mysql内核 innodb存储引擎卷1》中对此结构体做过描述,随着版本的变迁和mysql功能的递进,页控制块也进化了,非压缩页控制块(buf_block_struct)的管理与压缩页控制块(buf_page_struct)单独分开,前者包含后者的结构体引用、物理页帧地址、unzip_LRU链表节点(UT_LIST_NODE_T)、读写锁、互斥体等重要对象,是实现bufpool各种核心接口功能的最关键控制单元。

struct buf_block_struct{

       buf_page_t  page;            /*!<page information; this must

                                   bethe first field, so that

                                   buf_pool->page_hashcan point

                                   tobuf_page_t or buf_block_t */

       byte*            frame;          /*!< pointer to buffer frame which

                                   isof size UNIV_PAGE_SIZE, and

                                   alignedto an address divisible by

                                   UNIV_PAGE_SIZE*/

。。。。。。

};//本结构体中其他的部分都先不做任何的列举,但请一定记住上面这两个,page非压缩页结构体引用压缩页结构体的重要句柄,同时也是在一个重要函数中进行强制转换操作((buf_block_t*) bpage)最关键的部分!至于frame则是bufpool部门真正服务的核心所在,这是非压缩页(数据页,undo页,特殊页。。。。。。)的页帧地址。一旦某页记录读入通过read模块读入链表进行管理之后,那么它的所有modify操作等同于都是针对这个页帧里面做内存修改,至于写回磁盘是异步(同步)IO需要考虑的事情(详解文件存储子系统的时候会对IO机制作出完整说明)。

压缩页控制块(buf_page_struct),理论上讲,全部的非压缩页只是压缩页的子集(实际情况有待本人进一步验证),因为在进行核心操作的时候,都是在非压缩页中进行,因此压缩页控制块(buf_page_struct)并不包含互斥体,但是为了保证与(buf_block_struct)的一致,需要进行锁计数器的实现,另外,它还包含对应物理页的表空间id、表空间内页的偏移量、页状态、刷新类型(上文介绍过BUF_FLUSH_LRU和BUF_FLUSH_LIST)、压缩页对应引用、所在hash表、5大链表(unzip_LRU在非压缩页结构体对象(buf_block_struct)所以此处才是剩下的5个链表)的子节点(UT_LIST_NODE_T)、以及是否处于OLD_LRU端(LRU链表结构old部分)是否崩溃页初次访问时间等细节,这些也仅仅是列举出的最重要结构体对象而已,不是全部。

struct buf_page_struct{

       unsigned      space:32;     /*!<tablespace id; also protected

                                   bybuf_pool->mutex. */

       unsigned      offset:32;     /*!<page number; also protected

                                   bybuf_pool->mutex. */

。。。。。。

       unsigned      flush_type:2;      /*!< if this block is currently being

                                   flushedto disk, this tells the

                                   flush_type.

                                   @seeenum buf_flush */

       unsigned      io_fix:2;       /*!<type of pending I/O operation;

                                   alsoprotected by buf_pool->mutex

                                   @seeenum buf_io_fix */

       unsigned      buf_fix_count:19;/*!< count of howmanyfold this block

                                   iscurrently bufferfixed */

。。。。。。

       UT_LIST_NODE_T(buf_page_t)free;

       UT_LIST_NODE_T(buf_page_t)flush_list;

       UT_LIST_NODE_T(buf_page_t)zip_list;

。。。。。。

};

(3)六个重要链表


再次,子系统组件与基础控制结构体运作的过程要实现各种复杂功能,必然无法脱离6个重要的链表,

空闲链表(buf_pool->free),bufpool最重要的三个链表之一,上文已经多次提到LRU和flush等链表,但未提到空闲链表,实际上它是在系统初始化阶段bufpool进行初始化后唯一显式调用链表初始化函数进行init操作的唯一bufpool链表。系统内的第一个LRU链表块,必然是从free链表中获取到的,当flush模块脏页刷新完成,LRU链表节点就会被清除或者移动到LRU链表结尾等待清除,清除LRU之后的节点仍旧是回归到free链表内。

LRU链表(buf_pool->LRU),bufpool最重要的三个链表之二,当有LRU链表为空时,必然从free链表获取空闲节点,并进行异步IO读将页读入bufpool,并加入LRU链表,LRU链表长度过大的情况下,会进行尾部刷新,刷新失败会进行更彻底的直接通过LRU进行脏页刷新(BUF_FLUSH_LRU方式),flush链表节点得到释放脏页完成刷新,并同时把LRU链表的脏块也完成移除。可以说LRU和free、flush三个链表、5个模块有之间有着千丝万缕的联系。

非压缩LRU链表(buf_pool->unzip_LRU),本链表实际是LRU链表的一个子集,在压缩页控制块(buf_page_struct)中的压缩页需要进行解压缩以进行各种记录级读写操作时,该链表将发挥作用,因此可以说,插入到了unzip_LRU链表就一定页在LRU链表中,反之则未必。

脏块链表(buf_pool->flush_list),bufpool最重要的三个链表之二,实际上也是LRU链表的子集,所以读入bufpool的页都通过压缩的或者非压缩的控制块进行管理,最初一定是在LRU链表中,当事务部分(和mini事务部分)完成commit操作时候,实际上就意味着内存写的成功,脏页必然要加入flush链表,并等待异步IO线程(系统主线程子系统组成之一)进行刷新操作(linux原生异步IO和作者Heikki Tuuri自己用条件变量实现的模拟异步IO,buf_pool_struct提到过,存储部分会做详细说明)动作。

未修改压缩块链表(buf_pool->zip_clean),本链表以目前的源代码来看仅用于调试功能。

伙伴系统空闲链表(buf_pool->zip_free[]),bufpool6大链表中最特殊的一个,链表的根节点可以看做“是一个指针数组”,伙伴系统的精髓就在于按照2的倍数进行紧邻内存块的合并和拆分,进而达到高效管理、代码复杂度低的效果。这个指针数组按照块大小实际包含4层,1024,2048,4096和8192,每一层基结点只管理同类大小的块。


关于bufpool部分的整体概述,暂时告一段落,后续还会陆续添加新的内容并修正本帖错误和补足缺失内容,作进一步整理。比如说doublewrite,doublewrite属于一个很独立的模块,可以看做是文件存储系统的一部分,但他在事务章节和bufpool的flush组件实现中又占有非常重要的位置,目前还不好将它归类到哪个子系统的组件,bufpool后面讲到flush的时候,会一起做出部分的讲解。

老刘会坚持把这个分析一直做下去。如有兴趣的朋友也可以QQ加本人私聊:275787374,如发现文章中的任何错误欢迎朋友们指正。

下一部分要发布的章节为《mysql内核源代码深度解析 缓冲池 LRU模块 全面分析》。

以上是关于原创mysql内核源代码深度解析 缓冲池 buffer pool 整体概述的主要内容,如果未能解决你的问题,请参考以下文章

skb解析

mysql缓冲池— buffer pool

Java字符串池(String Pool)深度解析

Linux内核中sk_buff结构详解

Linux内核中sk_buff结构详解

Linux内核中sk_buff结构详解