第20節 從庫MTS多執行緒並行回放(二)

gaopengtttt發表於2020-01-09

從庫MTS多執行緒並行回放(二)


歡迎關注我的《深入理解MySQL主從原理 32講 》,如下:

image.png

如果圖片不能顯示可檢視下面連結:
https://www.jianshu.com/p/d636215d767f

本節包含一個筆記如下:
https://www.jianshu.com/p/e920a6d33005


這一節會先描述MTS的工作執行緒執行Event的大概流程。然後重點描述一下MTS中檢查點的概念。在後面的第25節我們可以看到,MTS的異常恢復很多情況下需要依賴這個檢查點,從檢查點位置開始掃描relay log做恢復操作,但是在GTID AUTO_POSITION MODE模式且設定了recovery_relay_log=1的情況下這種依賴將會弱化。

一、工作執行緒執行Event

前面我們已經討論了協調執行緒分發Event的規則,實際上協調執行緒只是將Event分發到了工作執行緒的執行佇列中。那麼工作執行緒執行Event就需要從執行佇列中拿出這些Event,然後進行執行。整個過程可以參考函式slave_worker_exec_job_group。因為這個流程比較簡單,因此就不需要畫圖了,但是我們需要關注一些點如下:

(1)從執行佇列中讀取Event。注意這裡如果執行佇列中沒有Event那麼就進入空閒等待,也就是工作執行緒處於無事可做的狀態,等待狀態為‘Waiting for an event from Coordinator’。

(2)如果執行到XID_EVENT那麼說明事務已經結束了那麼需要完成記憶體資訊更新操作。可參考Slave_worker::slave_worker_exec_event和Xid_apply_log_event::do_apply_event_worker函式。更新記憶體相關資訊可參考函式commit_positions函式。下面是一些更新的資訊,我們可以看到和slave_worker_info表中的資訊基本一致,如下:

1、更新當前資訊
strmake(group_relay_log_name, ptr_g->group_relay_log_name,
sizeof(group_relay_log_name) - 1);
group_relay_log_pos= ev->future_event_relay_log_pos;
set_group_master_log_pos(ev->common_header->log_pos);
set_group_master_log_name(c_rli->get_group_master_log_name());
2、將檢查點資訊進行寫入:
strmake(checkpoint_relay_log_name, ptr_g-
>checkpoint_relay_log_name,sizeof(checkpoint_relay_log_name) - 1);
checkpoint_relay_log_pos= ptr_g->checkpoint_relay_log_pos;
strmake(checkpoint_master_log_name, ptr_g-
>checkpoint_log_name,sizeof(checkpoint_master_log_name) - 1);
checkpoint_master_log_pos= ptr_g->checkpoint_log_pos;
3、設定GAQ序號:
 checkpoint_seqno= ptr_g->checkpoint_seqno;
更新整個BITMAP,可能已經由檢查點進行GAQ出隊:
for (uint pos= ptr_g->shifted; pos < c_rli->checkpoint_group; pos++) 
//重新設定點陣圖 因為checkpoint已經 
{                                                                     
//ptr_g->shifted是GAQ中出隊的事務個數
if (bitmap_is_set(&group_shifted, pos))                            
//這裡就需要偏移掉出隊的事務,恢復已經不需要了
bitmap_set_bit(&group_executed, pos - ptr_g->shifted);
}
4、設定點陣圖:
bitmap_set_bit(&group_executed, ptr_g->checkpoint_seqno);
//在本次事務相應的位置設定為1

(3)如果執行到XID_EVENT那麼說明事務已經結束了那麼需要完成記憶體資訊的持久化,即強制刷記憶體資訊持久化到slave_worker_info表中(relay_log_info_repository設定為TABLE)。可參考函式commit_positions函式,如下:

if ((error= w->commit_positions(this, ptr_group,
w->is_transactional())))

(4)如果執行到XID_EVENT還需要進行事務的提交操作,也就是進行Innodb層事務的提交。

從上面我們可以看到MTS中每次事務的提交併不會更新slave_relay_log_info表,而是進行slave_worker_info表的更新,將最新的資訊寫入到slave_worker_info表中。
我們前面也說過SQL執行緒已經蛻變為協調執行緒,那麼slave_relay_log_info表什麼時候更新呢?下面我們就能看到slave_relay_log_info表的更新實際上由協調執行緒在做完檢查點之後更新。

二、MTS中檢查點中的重要概念

總的說來MTS中的檢查點是MTS進行異常恢復的起點。實際上就是代表到這個位置之前(包含自身)事務都是已經在從庫執行過了,但之後的事務可能執行完成了也可能沒有執行完成。檢查點由協調執行緒進行。

(1)協調執行緒的GAQ佇列

前面我們已經知道MTS中為每個工作執行緒維護了一個Event的分發佇列。除此之外協調執行緒還維護了一個非常的重要的佇列GAQ,它是一個環形佇列。下面是原始碼中的定義:

  /*
    master-binlog ordered queue of Slave_job_group descriptors of groups
    that are under processing. The queue size is @c checkpoint_group. Group assigned
  */
  Slave_committed_queue *gaq;

每次協調執行緒分發事務的時候都會將事務記錄到GAQ佇列中,因此GAQ中事務的順序總是和relay log檔案中事務的順序一致的。檢查點正是作用在GAQ佇列上的,每次檢查點的位置稱為LWM,還記得上一節我叫大家先忽略的LWM嗎?就是這個。原始碼中定義也正是如此,它在GAQ佇列中進行維護。如下:

  /*
     The last checkpoint time Low-Water-Mark
  */
  Slave_job_group lwm;

在GAQ佇列中還維護有一個叫做checkpoint_seqno的序號,它是最後一次檢查點以來每個分配事務的序號,下面是原始碼中的定義:

uint checkpoint_seqno;  // counter of groups executed after the most recent CP

在協調執行緒讀取到GTID_LOG_EVENT後為其分配序號,記做checkpoint_seqno,如下:

rli->checkpoint_seqno++;//增加seqno

當協調執行緒進行檢查點的時候checkpoint_seqno序號會減去出隊的事務數量,如下:

checkpoint_seqno= checkpoint_seqno - shift; //這裡減去出隊的事務

在MTS異常恢復的時候也會用到這個序號,每個工作執行緒會通過這個序號來確認本工作執行緒執行事務的上限,如下:

      for (uint i= (w->checkpoint_seqno + 1) - recovery_group_cnt,
                 j= 0; i <= w->checkpoint_seqno; i++, j++)
            {
              if (bitmap_is_set(&w->group_executed, i))
//如果這一位 已經設定
              {
                DBUG_PRINT("mts", ("Setting bit %u.", j));
                bitmap_fast_test_and_set(groups, j);
//那麼GTOUPS 這個 bitmap中應該設定,最終GTOUPS會包含全的需要恢復的事務
              }
            }

關於詳細的異常恢復流程將在第25節描述。

(2)工作執行緒的Bitmap

有了GAQ佇列和檢查點就知道異常恢復開始的位置了。但是我們並不知道每一個工作執行緒都完成了哪些事務,哪些又沒有執行完成,因此就不能確認哪些事務需要恢復。在MTS中並行回放事務的提交併不是按分發順序的進行的,某些大事務(或者其他原因比鎖堵塞)可能遲遲不能提交,而一些小事務卻會很快提交完成。這些遲遲不能提交的事務就成為了所謂的’gap’,如果使用了GTID那麼在檢視已經執行GTID SET的時候可能出現一些‘空洞’,為了防止’gap’的發生通常需要設定引數slave_preserve_commit_order。下一節我們將會看到這種‘空洞’以及slave_preserve_commit_order的作用。但是如果要設定了slave_preserve_commit_order引數就需要開啟從庫記錄binary log的功能,因此必須開啟log_slave_updates引數。下面是原始碼的判斷:

  if (opt_slave_preserve_commit_order && rli->opt_slave_parallel_workers > 0 &&
      opt_bin_log && opt_log_slave_updates)
    commit_order_mngr= new Commit_order_manager(rli->opt_slave_parallel_workers);
//order commit 管理器

這裡先提前說一下MTS恢復的會有兩個關鍵階段:

  • 掃描階段

通過掃描檢查點以後的relay log。通過每個工作執行緒的Bitmap區分出哪些事務已經執行完成,哪些事務沒有執行完成,並且彙總形成恢復Bitmap,同時得到需要恢復的事務總量。

  • 執行階段

通過這個彙總的恢復Bitmap,將這些沒有執行完成事務讀取relay log再次執行。

這個Bitmap點陣圖和GAQ中的事務一一對應。當執行XID_EVENT完成提交後這一位將會被設定為‘1’。

(3)協調執行緒資訊的持久化

這個已經在前面提到過,實際上每次進行檢查點的時候都需要將檢查點的位置固化到slave_relay_log_info表中(relay_log_info_repository設定為TABLE)。因此slave_relay_log_info中儲存的實際上不是實時的資訊而是檢查點的資訊。下面就是slave_relay_log_info表的表結構:

mysql> desc slave_relay_log_info;
+-------------------+---------------------+------+-----+---------+-------+
| Field             | Type                | Null | Key | Default | Extra |
+-------------------+---------------------+------+-----+---------+-------+
| Number_of_lines   | int(10) unsigned    | NO   |     | NULL    |       |
| Relay_log_name    | text                | NO   |     | NULL    |       |
| Relay_log_pos     | bigint(20) unsigned | NO   |     | NULL    |       |
| Master_log_name   | text                | NO   |     | NULL    |       |
| Master_log_pos    | bigint(20) unsigned | NO   |     | NULL    |       |
| Sql_delay         | int(11)             | NO   |     | NULL    |       |
| Number_of_workers | int(10) unsigned    | NO   |     | NULL    |       |
| Id                | int(10) unsigned    | NO   |     | NULL    |       |
| Channel_name      | char(64)            | NO   | PRI | NULL    |       |
+-------------------+---------------------+------+-----+---------+-------+

與此同時show slave status中的某些資訊也是檢查點的記憶體資訊。下面的資訊將是來自檢查點:

  • Relay_Log_File :最新一次檢查點的relay log檔名。
  • Relay_Log_Pos :最新一次檢查點的relay log位點。
  • Relay_Master_Log_File:最新一次檢查點的主庫binary log檔名。
  • Exec_Master_Log_Pos:最新一次檢查點的主庫binary log位點。
  • Seconds_Behind_Master:根據檢查點指向事務的提交時間計算的延遲。

需要注意的是我們的GTID模組獨立在這一套理論之外,在第3節我們講GTID模組的初始化的時候我們就說過GTID模組的初始化是在從庫資訊初始化之前就完成了。因此在做MTS異常恢復的時候使用GTID AUTO_POSITION MODE模式將會變得更加簡單和安全,細節將在第25節描述。

(4)工作執行緒資訊的持久化

工作執行緒的資訊就持久化在slave_worker_info 表中,前面我們描述工作執行緒執行Event注意點的時候已經做了相應的描述。執行XID_EVENT完成事務提交之後會將資訊寫入到slave_worker_info 表中(relay_log_info_repository設定為TABLE)。其中包括資訊:

  • Relay_log_name:工作執行緒最後一個提交事務的relay log檔名。
  • Relay_log_pos:工作執行緒最後一個提交事務的relay log位點。
  • Master_log_name:工作執行緒最後一個提交事務的主庫binary log檔名。
  • Master_log_pos:工作執行緒最後一個提交事務的主庫binary log檔案位點。
  • Checkpoint_relay_log_name:工作執行緒最後一個提交事務對應檢查點的relay log檔名。
  • Checkpoint_relay_log_pos:工作執行緒最後一個提交事務對應檢查點的relay log位點。
  • Checkpoint_master_log_name:工作執行緒最後一個提交事務對應檢查點的主庫binary log檔名。

  • Checkpoint_master_log_pos:工作執行緒最後一個提交事務對應檢查點的主庫binary log位點。

  • Checkpoint_seqno:工作執行緒最後一個提交事務對應checkpoint_seqno序號。

  • Checkpoint_group_size:工作執行緒的Bitmap位元組數,約等於 GAQ佇列大小/8,因為1個位元組為8位。
  • Checkpoint_group_bitmap:工作執行緒對應的Bitmap點陣圖資訊。

關於Checkpoint_group_size的換算參考函式Slave_worker::write_info。

(5)兩個引數
  • slave_checkpoint_group:GAQ佇列大小。
  • slave_checkpoint_period:多久執行一次檢查點,預設300毫秒。
(6)檢查點執行的時機
  • 超過slave_checkpoint_period配置。可參考next_event函式如下:
if (rli->is_parallel_exec() && (opt_mts_checkpoint_period != 0 || force))
{
ulonglong period= static_cast<ulonglong>(opt_mts_checkpoint_period * 1000000ULL);
...
(void) mts_checkpoint_routine(rli, period, force, true/*need_data_lock=true*/);
...
      }
  • 達到GAQ佇列已滿,如下:
 //如果達到了 GAQ的大小 設定為force 強制checkpoint 
bool force= (rli->checkpoint_seqno > (rli->checkpoint_group - 1));
  • 正常stop slave。
(7)一個列子

通常有壓力的情況下的slave_worker_info中的所有工作執行緒最大的Checkpoint_master_log_pos應該和slave_relay_log_info中的Master_log_pos 相等,因為這是最後一個檢查點的位點資訊,如下:

三、MTS中的檢查點的流程

這一部分將詳細描述一下檢查點的步驟,關於檢查點可以參考函式mts_checkpoint_routine。

假設現在有7個事務是可以並行執行的,工作執行緒數量為4個。當前協調執行緒已經分發了5個,前面4個事務都已經執行完成,其中第5的一個事務是大事務。那麼可能當前的狀態圖如下(圖20-1,高清原圖包含在文末原圖中):

20-1.png

前面4個事務每個工作執行緒都分到一個,最後一個大事務這裡假設由工作執行緒2進行執行,圖中用紅色部分表示。

(1)判斷是超過了slave_checkpoint_period設定的大小,如果超過需要進行檢查點。
  if (!force && diff < period)
//是否需要進行檢查點是否超過了slave_checkpoint_period的設定
  {
    /*
      We do not need to execute the checkpoint now because
      the time elapsed is not enough.
    */
    DBUG_RETURN(FALSE);
  }
(2)掃描GAQ佇列進行出隊操作,直到第一個沒有提交的事務為止。圖中紅色部分就是一個大事務,檢查點只能停留在它之前。
cnt= rli->gaq->move_queue_head(&rli->workers); 
//work陣列 返回出隊的個數

move_queue_head部分程式碼如下:

    if (ptr_g->worker_id == MTS_WORKER_UNDEF ||
        my_atomic_load32(&ptr_g->done) == 0) 
//當前GROUP是否已經執行完成 如果沒有執行完成就需要 停止本次檢查點
      break; /* 'gap' at i'th */
(3)更新記憶體和relay_log_info_repository表的資訊為本次檢查點指向的位置。

先更新記憶體資訊,也就是我們show slave status中看到的資訊:

  rli->set_group_master_log_pos(rli->gaq->lwm.group_master_log_pos);
  rli->set_group_relay_log_pos(rli->gaq->lwm.group_relay_log_pos);
  rli->set_group_relay_log_name(rli->gaq->lwm.group_relay_log_name);

然後強制寫入表slave_relay_log_info中:

error= rli->flush_info(TRUE); 
//將本次檢查點資訊 寫入到relay_log_info_repository表中
(4)更新last_master_timestamp資訊為檢查點位置事務的XID_EVENT的timstamp值

這個值在第27節中會詳細描述,它是計算Seconds_behind_master的一個因素:

/*
    Update the rli->last_master_timestamp for reporting correct Seconds_behind_master.
    If GAQ is empty, set it to zero.
    Else, update it with the timestamp of the first job of the Slave_job_queue
    which was assigned in the Log_event::get_slave_worker() function.
  */
ts= rli->gaq->empty()? 0 : reinterpret_cast<Slave_job_group*>(rli->gaq->head_queue())->ts;
//rli->gaq->head_queue 檢查點位置的GROUP的時間
rli->reset_notified_checkpoint(cnt, ts, need_data_lock, true);
reset_notified_checkpoint函式中有:
last_master_timestamp= new_ts;

因此MTS中Seconds_behind_master的計算和檢查點息息相關。

(5)最後還會將前面GAQ出隊的事務數量進行統計,因為每個工作執行緒需要根據這個值來進行Bitmap點陣圖的偏移。並且還會維護我們前面說的GAQ的checkpoint_seqno值。

這個操作也是在函式Relay_log_info::reset_notified_checkpoint中完成的,實際上很簡單部分程式碼如下:

for (Slave_worker **it= workers.begin(); it != workers.end(); ++it)
//迴圈每個woker
w->bitmap_shifted= w->bitmap_shifted + shift; 
//每個worker執行緒都會增加 這個偏移量
checkpoint_seqno= checkpoint_seqno - shift; 
//這裡減去 移動的個數

到這裡整個檢查點的基本操作就完成了。我們看到實際上步驟並不多,拿到Bitmap偏移量後每個工作執行緒就會在隨後的第一個事務提交的時候進行點陣圖的偏移,checkpoint_seqno 計數也會更新。

我們前面的假設環境中,如果觸發了一次檢查點,並且協調執行緒將後兩個可以並行的事務發給了工作執行緒1和3進行處理並且處理完成。那麼我們的圖會變成如下(圖20-2,高清原圖包含在文末原圖中):

20-2.png

這張圖中我用不同樣色表示了不同線條,因為它們交叉比較多。GAQ中的紅色事務就是我們假設的大事務它仍然沒有執行完成,它也是我們所謂的‘gap’。如果這個時候MySQL例項異常重啟,那麼這個紅色‘gap’就是我們啟動後需要找到的事務,方式就是通過Bitmap點陣圖進行比對,後面說異常恢復的時候再詳細討論。如果是開啟了GTID,這種‘gap’很容易就能觀察到,下一節將進行測試。
同時我們需要注意這個時候工作執行緒2並沒有分發新的事務執行,因為工作執行緒2沒有執行完大事務, 因此在slave_woker_info表中它的資訊仍然顯示為上一次提交事務的資訊。而工作執行緒4因為沒有分配到新的事務,因此slave_woker_info表中它的資訊也顯示為上一次提交事務的資訊。因此在slave_woker_info中工作執行緒2和工作執行緒4的檢查點資訊、Bitmap資訊、checkpoint_seqno都是老的資訊。

總結

好了到這裡我已經說明了MTS中三個關鍵點

  • 協調執行緒是根據什麼規則進行事務分發的。
  • 工作執行緒如何拿到分發的事務。
  • MTS中的檢查點是如何進行的。

但是還有一個關鍵點沒有說,就是前面多次提到的異常恢復,第25節將重點解釋。


第20節結束

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2672625/,如需轉載,請註明出處,否則將追究法律責任。

相關文章