工作线程的入口函数 buf_flush_page_cleaner_worker
buf_flush_page_cleaner_worker工作线程的主循环启动后就等在page_cleaner_t的is_requested事件上,一旦协调线程通过is_requested唤醒所有等待的工作线程,工作线程就调用pc_flush_slot()函数去完成刷脏动作。
pc_request、pc_flush_slot以及pc_wait_finished这三个核心函数的实现
request这个函数的作用主要就是为每个slot代表的缓冲池实例计算要刷脏多少页;然后把每个slot的state设置PAGE_CLEANER_STATE_REQUESTED;把n_slots_requested设置成当前slots的总数,也即缓冲池实例的个数,同时把n_slots_flushing和n_slots_finished清0,然后唤醒等待的工作线程。这个函数只会在协调线程里调用,其核心代码如下:
mutex_enter(&page_cleaner->mutex); //由于page_cleaner是全局的,在修改之前先获取互斥锁
page_cleaner->requested = (min_n > 0); //是否需要对flush_list进行刷脏操作,还是只需要对LRU列表刷脏
page_cleaner->lsn_limit = lsn_limit; // 设置lsn_limit, 只有数据页的oldest_modification小于它的才会刷出去
for (ulint i = 0; i < page_cleaner->n_slots; i++) {
page_cleaner_slot_t* slot = &page_cleaner->slots[i];
//为两种特殊情况设置每个slot需要刷脏的页数,当为ULINT_MAX表示服务器比较空闲,则刷脏线程可以尽可能的把当前的所有脏页都刷出去;而当为0是,表示没有脏页可刷。
if (min_n == ULINT_MAX) {
slot->n_pages_requested = ULINT_MAX;
} else if (min_n == 0) {
slot->n_pages_requested = 0;
}
slot->state = PAGE_CLEANER_STATE_REQUESTED; //在唤醒刷脏工作线程之前,将每个slot的状态设置成requested状态
}
// 协调线程在唤醒工作线程之前,设置请求要刷脏的slot个数,以及清空正在刷脏和完成刷脏的slot个数。只有当完成的刷脏个数等于总的slot个数时,才表示次轮的刷脏结束。
page_cleaner->n_slots_requested = page_cleaner->n_slots;
page_cleaner->n_slots_flushing = 0;
page_cleaner->n_slots_finished = 0;
os_event_set(page_cleaner->is_requested);
mutex_exit(&page_cleaner->mutex);
pc_flush_slot是刷脏线程真正做刷脏动作的函数,协调线程和工作线程都会调用。由于刷脏线程和slot并不是事先绑定对应的关系。所以工作线程在刷脏时首先会找到一个未被占用的slot,修改其状态,表示已被调度,然后对该slot所对应的缓冲池instance进行操作。直到所有的slot都被消费完后,才进入下一轮。通过这种方式,多个刷脏线程实现了并发刷脏缓冲池。一旦找到一个未被占用的slot,则需要把全局的page_cleaner里的n_slots_rqeusted减1、把n_slots_flushing加1,同时这个slot的状态从PAGE_CLEANER_STATE_REQUESTED状态改成PAGE_CLEANER_STATE_FLUSHING。然后分别调用buf_flush_LRU_list() 和buf_flush_do_batch() 对LRU和flush_list刷脏。刷脏结束把n_slots_flushing减1,把n_slots_finished加1,同时把这个slot的状态从PAGE_CLEANER_STATE_FLUSHING状态改成PAGE_CLEANER_STATE_FINISHED状态。同时若这个工作线程是最后一个完成的,则需要通过is_finished事件,通知协调进程所有的工作线程刷脏结束。 已删除流程无关代码代码,其核心代码如下:
for (i = 0; i < page_cleaner->n_slots; i++) { //由于slot和刷脏线程不是事先定好的一一对应关系,所以在每个工作线程开始要 先找到一个未被处理的slot
slot = &page_cleaner->slots[i];
if (slot->state == PAGE_CLEANER_STATE_REQUESTED) {
break;
}
}
buf_pool_t* buf_pool = buf_pool_from_array(i); // 根据找到的slot,对应其缓冲池的实例
page_cleaner->n_slots_requested--; // 表明这个slot开始被处理,将未被处理的slot数减1
page_cleaner->n_slots_flushing++; //这个slot开始刷脏,将flushing加1
slot->state = PAGE_CLEANER_STATE_FLUSHING; // 把这个slot的状态设置为flushing状态
if (page_cleaner->n_slots_requested == 0) { //若是所有的slot都处理了,则清楚is_requested的通知标志
os_event_reset(page_cleaner->is_requested);
}
/* Flush pages from end of LRU if required */
slot->n_flushed_lru = buf_flush_LRU_list(buf_pool); // 开始刷LRU队列
/* Flush pages from flush_list if required */
if (page_cleaner->requested) { // 刷flush_list队列
slot->succeeded_list = buf_flush_do_batch(
buf_pool, BUF_FLUSH_LIST,
slot->n_pages_requested,
page_cleaner->lsn_limit,
&slot->n_flushed_list);
} else {
slot->n_flushed_list = 0;
slot->succeeded_list = true;
}
page_cleaner->n_slots_flushing--; // 刷脏工作线程完成次轮刷脏后,将flushing减1
page_cleaner->n_slots_finished++; //刷脏工作线程完成次轮刷脏后,将完成的slot加一
slot->state = PAGE_CLEANER_STATE_FINISHED; // 设置此slot的状态为FINISHED
if (page_cleaner->n_slots_requested == 0
&& page_cleaner->n_slots_flushing == 0) {
os_event_set(page_cleaner->is_finished); // 当所有的工作线程都完成了刷脏,要通知协调进程,本轮刷脏完成
}
pc_wait_finished函数的主要由协调线程调用,它主要用来收集每个工作线程分别对LRU和flush_list列表刷脏的页数。以及为每个slot清0次轮请求刷脏的页数和重置它的状态为NONE。
os_event_wait(page_cleaner->is_finished); // 协调线程通知工作线程和完成自己的刷脏任务之后,要等在is_finished事件上,知道最后一个完成的工作线程会set这个事件唤醒协调线程
mutex_enter(&page_cleaner->mutex);
for (ulint i = 0; i < page_cleaner->n_slots; i++) {
page_cleaner_slot_t* slot = &page_cleaner->slots[i];
ut_ad(slot->state == PAGE_CLEANER_STATE_FINISHED);
// 统计每个slot分别通过LRU和flush_list队列刷出去的页数
*n_flushed_lru += slot->n_flushed_lru;
*n_flushed_list += slot->n_flushed_list;
all_succeeded &= slot->succeeded_list;
// 把所有slot的状态设置为NONE
slot->state = PAGE_CLEANER_STATE_NONE;
//为每个slot清除请求刷脏的页数
slot->n_pages_requested = 0;
}
// 清零完成的slot刷脏个数,为下一轮刷脏重新统计做准备
page_cleaner->n_slots_finished = 0;
// 清除is_finished事件的通知标志
os_event_reset(page_cleaner->is_finished);
mutex_exit(&page_cleaner->mutex);
总结
在MySQL 5.7中,Innodb通过定义page_cleaner_t, page_cleaner_slot_t 和 page_cleaner_state_t等数据结构,以及pc_request、pc_flush_slot和pc_wait_finished等函数实现了多线程的刷脏,提高了刷脏的效率,尽可能的避免用户线程参与刷脏。
参考
MySQL · 引擎特性 · InnoDB Buffer Pool




