1. 程式人生 > >mysql核心原始碼深度解析 緩衝池 buffer pool 整體概述(bufferpool部分一)

mysql核心原始碼深度解析 緩衝池 buffer pool 整體概述(bufferpool部分一)

老劉原創文章,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,如發現文章中的任何錯誤歡迎朋友們指正。