共計 4982 個字符,預計需要花費 13 分鐘才能閱讀完成。
這篇文章主要介紹 MySQL 半同步復制的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
代碼分析
int repl_semi_report_commit(Trans_param *param)//gdb 下 param?
{
bool is_real_trans= param- flags TRANS_IS_REAL_TRANS;
if (is_real_trans param- log_pos)
{
const char *binlog_name= param- log_file;
return repl_semisync.commitTrx(binlog_name, param- log_pos);
}
return 0;
}
ol start= 1 >
int ReplSemiSyncMaster::commitTrx(const char* trx_wait_binlog_name,
my_off_t trx_wait_binlog_pos)
{
// 自旋鎖,下面的代碼是線性執行。
mysql_mutex_lock(LOCK_binlog_);
if (active_tranxs_ != NULL trx_wait_binlog_name){
entry=active_tranxs_- find_active_tranx_node(trx_wait_binlog_name,
trx_wait_binlog_pos);
if (entry)
thd_cond= entry- cond;
}
// 進入信號了,為后面發起信號量的等待動作做準備,每個正在進行提交的事務都對應一個初始化的信號量 thd_cond
THD_ENTER_COND(NULL, thd_cond, LOCK_binlog_,
stage_waiting_for_semi_sync_ack_from_slave,
old_stage);
if (getMasterEnabled() trx_wait_binlog_name){
set_timespec(start_ts, 0);//
if (!getMasterEnabled() || !is_on())
goto l_end;
// 計算等待 ACK 的截止時間。按照當前時間加上半同步等待的超時時間,這個時間回在發起信號量等待的時候用的
//rpl_semi_sync_master_timeout
abstime.tv_sec = start_ts.tv_sec + wait_timeout_ / TIME_THOUSAND;
abstime.tv_nsec = start_ts.tv_nsec +(wait_timeout_ % TIME_THOUSAND) * TIME_MILLION;
if (abstime.tv_nsec = TIME_BILLION){
abstime.tv_sec++;
abstime.tv_nsec -= TIME_BILLION;
}
//state_是 TRUE 表示當前半同步狀態為 on,否則直接進入 l_end。Rpl_semi_sync_master_status
//reply_file_name_值的變化,在其他函數中?
while (is_on()){
if (reply_file_name_inited_){
// 比較事務所涉及的 binlog 位置跟 reply 的位置,如果 cmp 0,說明此事務的 binlog 已經同步
// 到 slave,跳出該循環,進入最后階段 l_end
int cmp = ActiveTranx::compare(reply_file_name_, reply_file_pos_,
trx_wait_binlog_name, trx_wait_binlog_pos);
if (cmp = 0){
break;
}
}
if (wait_file_name_inited_){
// 比較事務所涉及的 binlog 位置和當前最小需要等待的 binlog 位置。如果 cmp 0,表示調整當前最小需要等待
//binlog 的位置。rpl_semi_sync_master_wait_pos_backtraverse++, 即等待位置需要調整的次數,一般不會
// 調整
int cmp = ActiveTranx::compare(trx_wait_binlog_name, trx_wait_binlog_pos,
wait_file_name_, wait_file_pos_);
if (cmp = 0){
strncpy(wait_file_name_, trx_wait_binlog_name, sizeof(wait_file_name_) – 1);
wait_file_name_[sizeof(wait_file_name_) – 1]= \0
wait_file_pos_ = trx_wait_binlog_pos;
rpl_semi_sync_master_wait_pos_backtraverse++;
}
}else{
// 保存第一次最小需要響應的事務位置
strncpy(wait_file_name_, trx_wait_binlog_name, sizeof(wait_file_name_) – 1);
wait_file_name_[sizeof(wait_file_name_) – 1]= \0
wait_file_pos_ = trx_wait_binlog_pos;
wait_file_name_inited_ = true;
}
// 如果 salve 個數是 0 了,則將半同步關閉,退出循環
if (abort_loop rpl_semi_sync_master_clients == 0 is_on()){
switch_off();
break;
}
// 正式進入等待 binlog 同步的步驟,將 rpl_semi_sync_master_wait_sessions+1,表明
// 有多少要提交的事務線程在等待(這個值是否能夠代表實際等待事務的線程數量,值得懷疑,因為該函數
// 開始位置有 lock,沒有 unlock 前,其他線程也進不到這一步,沒辦法執行 ++)
// 然后發起等待信號,進入信號等待后,只有 2 種情況可以退出等待。1 是被其他線程喚醒(binlog dump)
// 2 是等待超時時間。如果是被喚醒則返回值是 0,否則是其他值
rpl_semi_sync_master_wait_sessions++;
entry- n_waiters++;
// 發起信號等待,然后根據返回結果做相應計數:上面是循環體里面的所有內容,接下來我們看退出循環后的操作。特別提一下,喚醒該線程的 dump 線程,當 dump 線程收到相應 binlog 位置的 ack 之后,會將其喚醒。
wait_result= mysql_cond_timedwait(entry- cond, LOCK_binlog_, abstime);
entry- n_waiters–;
rpl_semi_sync_master_wait_sessions–;
if (wait_result != 0){
// 等待超時,關閉半同步
rpl_semi_sync_master_wait_timeouts++;
switch_off();
}else{
wait_time = getWaitTime(start_ts);
if (wait_time 0){
// 表明時鐘錯誤,可能是做了時間調整
rpl_semi_sync_master_timefunc_fails++;
}else{
// 將等待事件與該等待計入總數
rpl_semi_sync_master_trx_wait_num++;
rpl_semi_sync_master_trx_wait_time += wait_time;
}
}
}//end while
l_end:
/* Update the status counter. */
if (is_on())
rpl_semi_sync_master_yes_transactions++;
else
rpl_semi_sync_master_no_transactions++;
}
/* Last waiter removes the TranxNode */
if (trx_wait_binlog_name active_tranxs_
entry entry- n_waiters == 0)
active_tranxs_- clear_active_tranx_nodes(trx_wait_binlog_name,
trx_wait_binlog_pos);
THD_EXIT_COND(NULL, old_stage);
}
3、流程總結
1)在 commit 函數中,首先需要加一個自旋鎖 LOCK_binlog_,主要動作都在這個鎖內執行。
2)進入信號,為后面發起信號量的等待動作做準備
3)計算 binlog 等待 ACK 的截止時間。從此時開始 + 半同步等待的超時時間 rpl_semi_sync_master_timeout(默認是 10s)
4)需要在半同步狀態下進入下面操作,否則進入 l_end。半同步狀態的判斷是 state_,和 Rpl_semi_sync_master_status 是什么關系?
5)第一次進來:保存最小需要響應的事務位置 wait_file_name_、wait_file_pos_,并將 wait_file_name_inited_置成 TRUE
6)最大響應位置 reply_file_name_、reply_file_pos_在 binlog dump 線程修改,和當前 binlog(已經 flush 的?)的位置比較。若當前 binlog 位置比 reply 的小,表示次事務的 binlog 已經到 slave 了,跳出循環,進入最后階段 l_end
7)非第一次進來:比較事務涉及的 binlog 位置和當前最小需要等待的 binlog 位置。如果比 wai_file_name 的小,需要將最小需要等待的位置調整到當前位置。rpl_semi_sync_master_wait_pos_backtraverse++,即最小等待位置需要調整的次數。一般不會調整。
8)第一次進來:需要保存最小需要等待響應的位置為當前位置
9)接著,需要判斷 slave 個數和半同步是否正常。不正常則退出循環,將半同步關閉
10)正式進入等待 binlog 同步的步驟:
rpl_semi_sync_master_wait_sessions+1:表示有多少提交的事務線程正在等待
發起信號等待:mysql_cond_timedwait:只有 2 中情況可以退出等待:1 是被其他線程 binlog dump 喚醒,2 是等待超時。
特別提一下,喚醒該線程的 dump 線程,當 dump 線程收到相應 binlog 位置的 ack 之后,會將其喚醒。
等待超時:將半同步關閉
接收到 slave ACK,被 binlog dump 線程喚醒:修改對應變量
11)將 after_flush 步驟插入 active_trans 的 node 刪掉
12)直到最后一步才釋放鎖,因此該函數是整個實例串行的。同時中間有個信號等待的動作。如果數據庫并發量很大,而此時主從異常,一旦超時時間設置過大,則可能出現其他用戶線程阻塞在 lock() 函數上,杜塞時間越長,累積的線程越多,容易引發雪崩,所以超時時間設置需謹慎,并非隨意設置。
以上是“MySQL 半同步復制的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注丸趣 TV 行業資訊頻道!