Innodb缓冲池刷脏的多线程实现
简介
为了提高性能,大多数的数据库在操作数据时都不会直接读写磁盘,而是中间经过缓冲池,将要写入磁盘的数据先写入到缓冲池里,然后在某个时刻后台线程把修改的数据刷写到磁盘上。MySQL的InnoDB引擎也使用缓冲池来缓存从磁盘读取或修改的数据页,如果当前数据库需要操作的数据集比缓冲池中的空闲页面大的话,当前缓冲池中的数据页就必须进行脏页淘汰,以便腾出足够的空闲页面供当前的查询使用。如果数据库负载太高,对于空闲页面的需求超出了page cleaner的淘汰能力,这时候是否能够快速获取空闲页面,会直接影响到数据库的处理能力。5.6版本以前,脏页的清理工作交由master线程的;Page cleaner thread是5.6.2引入的一个新线程,它实现从master线程中卸下缓冲池刷脏页的工作;为了进一步提升扩展性和刷脏效率,在5.7.4版本里引入了多个page cleaner线程,从而达到并行刷脏的效果。目前Page cleaner并未和缓冲池绑定,有一个协调线程 和 多个工作线程,协调线程本身也是工作线程。工作队列长度为缓冲池实例的个数,使用一个全局slot数组表示。 下面以MySQL 5.7的5.7.23版本为例,分析具体多线程刷脏的源码实现。
提炼要点:
- 5.6版本以前,刷脏任务交由master-Thread处理
- 5.62,引入Page cleaner thread,负责缓冲池刷脏页的工作
- 5.7.4,提升为多个page cleaner线程,进行并行刷脏,这些线程都不与缓冲池绑定。
- 由一个协调线程与多个工作线程进行处理,工作队列长度为缓冲池实例的个数,使用一个全局slot数组表示。
以下列出部分源码进行解析:(涉及的数据结构:page_cleaner_t, page_cleaner_slot_t 和 page_cleaner_state_t)
//page_cleaner_t,
/** 这个结构体是由所有的刷脏线程共用的 */
struct page_cleaner_t {
//修改任何信息都要先获取互斥锁mutex字段
(暂时猜是由协调线程持有这个锁,才能进行管理)
ib_mutex_t mutex; /*!< mutex to protect whole of
page_cleaner_t struct and
page_cleaner_slot_t slots. */
//is_requested和is_finished event是分别
//用来唤醒工作线程和最后一个完成刷脏的工作线程通知协调线程这次的刷脏完成;
os_event_t is_requested; /*!< event to activate worker*/
os_event_t is_finished; /*!< event to signal that all
slots were finished. */
//当前刷脏工作线程的数量
volatile ulint n_workers;
//用来表示刷脏协调线程是否有脏页需要写到磁盘上
bool requested;
//lsn_limit表示需要刷新到lsn的位置,页的最早修改lsn必须小于这个值,它才能被刷出到磁盘上
lsn_t lsn_limit;
//需要刷脏的实例个数
ulint n_slots; /*!< total number of slots */
//一开始的个数
ulint n_slots_requested;
/*!< number of slots
in the state
PAGE_CLEANER_STATE_REQUESTED */
//正在刷的个数
ulint n_slots_flushing;
/*!< number of slots
in the state
PAGE_CLEANER_STATE_FLUSHING */
//完成的个数
ulint n_slots_finished;
/*!< number of slots
in the state
PAGE_CLEANER_STATE_FINISHED */
ulint flush_time; /*!< elapsed time to flush
requests for all slots */
ulint flush_pass; /*!< count to finish to flush
requests for all slots */
//每一个slot就是一个page_cleaner_slot_t结构; 记录着刷脏状态;
page_cleaner_slot_t* slots; /*!< pointer to the slots */
bool is_running; /*!< false if attempt
to shutdown */
};
总结:
- 这个数据结构是实现多刷脏线程的核心结构,包含了所有刷脏线程所需要的信息,以及刷脏协调线程和刷脏工作线程之间同步所需要的同步事件。
- 这个结构体是由所有的刷脏线程共用的,修改任何信息都要先获取互斥锁mutex字段。
- is_requested和is_finished event是分别用来唤醒工作线程和最后一个完成刷脏的工作线程通知协调线程这次的刷脏完成;n_workers表示刷脏工作线程的数目。
- requested用来表示刷脏协调线程是否有脏页需要写到磁盘上,若是没有的话,刷脏线程只需要对LRU列表中的页回收到空闲列表中;lsn_limit表示需要刷新到lsn的位置,页的最早修改lsn必须小于这个值,它才能被刷出到磁盘上。
最后是一些状态参数的说明:n_slots表示这些刷脏线程需要刷脏的缓冲池实例的个数;另外还有一个比较重要的字段slots,它用来记录刷脏线程对缓冲池刷脏的当前状态,每一个slot就是一个page_cleaner_slot_t结构; n_slots_requested/n_slots_flushing/n_slots_finished主要用在刷脏过程中记录所有刷脏线程处在各个阶段的线程数目,当一开始刷脏时协调线程会把n_slots_requested设置成当前slots的总数,也即缓冲池实例的个数,而会把n_slots_flushing和n_slots_finished清0。每当一个刷脏线程完成一个缓冲池实例的刷脏n_slots_requested会减1、n_slots_finished会加1。所有的刷脏线程完成后,n_slots_requested会为0,n_slots_finished会为slots的总数目。
page_cleaner_slot_t
/** Page cleaner request state for each buffer pool instance */
//这个结构体代表着slot的状态&#