MySQL半同步複製--after_commit

yzs87發表於2017-12-22

1、介紹

在分析程式碼前,先介紹binlog中幾個位置關係。

2、程式碼分析

  1. MYSQL_BIN_LOG::ordered_commit->finish_commit:  
  2.                          ha_commit_low  
  3.                          RUN_HOOK(transaction, after_commit, (thd, all))->  
  4.                         (after_commit)repl_semi_report_commit:commitTrx  
該函式在資料庫儲存引擎提交完成後,此時有可能等待從庫的訊息。
  1. int repl_semi_report_commit(Trans_param *param)//gdb下param?  
  2. {  
  3.   
  4.   bool is_real_trans= param->flags & TRANS_IS_REAL_TRANS;  
  5.   
  6.   if (is_real_trans && param->log_pos)  
  7.   {  
  8.     const char *binlog_name= param->log_file;  
  9.     return repl_semisync.commitTrx(binlog_name, param->log_pos);  
  10.   }  
  11.   return 0;  
  12. }
  1. int ReplSemiSyncMaster::commitTrx(const char* trx_wait_binlog_name,  
  2.                   my_off_t trx_wait_binlog_pos)  
  3. {  
  4.     //自旋鎖,下面的程式碼是線性執行。  
  5.     mysql_mutex_lock(&LOCK_binlog_);  
  6.     if (active_tranxs_ != NULL && trx_wait_binlog_name){  
  7.         entry=active_tranxs_->find_active_tranx_node(trx_wait_binlog_name,  
  8.                                              trx_wait_binlog_pos);  
  9.         if (entry)  
  10.             thd_cond= &entry->cond;  
  11.     }  
  12.     //進入訊號了,為後面發起訊號量的等待動作做準備,每個正在進行提交的事務都對應一個初始化的訊號量thd_cond  
  13.     THD_ENTER_COND(NULL, thd_cond, &LOCK_binlog_,  
  14.                  & stage_waiting_for_semi_sync_ack_from_slave,  
  15.                  & old_stage);  
  16.     if (getMasterEnabled() && trx_wait_binlog_name){  
  17.         set_timespec(start_ts, 0);//  
  18.         if (!getMasterEnabled() || !is_on())  
  19.             goto l_end;  
  20.         //計算等待ACK的截止時間。按照當前時間加上半同步等待的超時時間,這個時間回在發起訊號量等待的時候用的  
  21.         //rpl_semi_sync_master_timeout  
  22.         abstime.tv_sec = start_ts.tv_sec + wait_timeout_ / TIME_THOUSAND;  
  23.         abstime.tv_nsec = start_ts.tv_nsec +(wait_timeout_ % TIME_THOUSAND) * TIME_MILLION;  
  24.         if (abstime.tv_nsec >= TIME_BILLION){  
  25.             abstime.tv_sec++;  
  26.             abstime.tv_nsec -= TIME_BILLION;  
  27.         }  
  28.         //state_是TRUE表示當前半同步狀態為on,否則直接進入l_end。Rpl_semi_sync_master_status  
  29.         //reply_file_name_值的變化,在其他函式中?  
  30.         while (is_on()){  
  31.             if (reply_file_name_inited_){  
  32.                 //比較事務所涉及的binlog位置跟reply的位置,如果cmp>0,說明此事務的binlog已經同步  
  33.                 //到slave,跳出該迴圈,進入最後階段l_end  
  34.                 int cmp = ActiveTranx::compare(reply_file_name_, reply_file_pos_,  
  35.                                        trx_wait_binlog_name, trx_wait_binlog_pos);  
  36.                 if (cmp >= 0){  
  37.                     break;  
  38.                 }  
  39.             }  
  40.             if (wait_file_name_inited_){  
  41.                 //比較事務所涉及的binlog位置和當前最小需要等待的binlog位置。如果cmp<0,表示調整當前最小需要等待  
  42.                 //binlog的位置。rpl_semi_sync_master_wait_pos_backtraverse++,即等待位置需要調整的次數,一般不會  
  43.                 //調整  
  44.                 int cmp = ActiveTranx::compare(trx_wait_binlog_name, trx_wait_binlog_pos,  
  45.                                        wait_file_name_, wait_file_pos_);  
  46.                  if (cmp <= 0){  
  47.                     strncpy(wait_file_name_, trx_wait_binlog_name, sizeof(wait_file_name_) - 1);  
  48.                     wait_file_name_[sizeof(wait_file_name_) - 1]= '\0';  
  49.                     wait_file_pos_ = trx_wait_binlog_pos;  
  50.                     rpl_semi_sync_master_wait_pos_backtraverse++;  
  51.                  }  
  52.                    
  53.             }else{  
  54.                 //儲存第一次最小需要響應的事務位置  
  55.                 strncpy(wait_file_name_, trx_wait_binlog_name, sizeof(wait_file_name_) - 1);  
  56.                 wait_file_name_[sizeof(wait_file_name_) - 1]= '\0';  
  57.                 wait_file_pos_ = trx_wait_binlog_pos;  
  58.                 wait_file_name_inited_ = true;  
  59.             }  
  60.             //如果salve個數是0了,則將半同步關閉,退出迴圈  
  61.             if (abort_loop && rpl_semi_sync_master_clients == 0 && is_on()){  
  62.                 switch_off();  
  63.                 break;  
  64.             }  
  65.             //正式進入等待binlog同步的步驟,將rpl_semi_sync_master_wait_sessions+1,表明  
  66.             //有多少要提交的事務執行緒在等待(這個值是否能夠代表實際等待事務的執行緒數量,值得懷疑,因為該函式  
  67.             //開始位置有lock,沒有unlock前,其他執行緒也進不到這一步,沒辦法執行++)  
  68.             //然後發起等待訊號,進入訊號等待後,只有2種情況可以退出等待。1是被其他執行緒喚醒(binlog dump)  
  69.             //2是等待超時時間。如果是被喚醒則返回值是0,否則是其他值  
  70.             rpl_semi_sync_master_wait_sessions++;  
  71.             entry->n_waiters++;  
  72.             //發起訊號等待,然後根據返回結果做相應計數:上面是迴圈體裡面的所有內容,接下來我們看退出迴圈後的操作。特別提一下,喚醒該執行緒的dump執行緒,當dump執行緒收到相應binlog位置的ack之後,會將其喚醒。  
  73.             wait_result= mysql_cond_timedwait(&entry->cond, &LOCK_binlog_, &abstime);  
  74.             entry->n_waiters--;  
  75.             rpl_semi_sync_master_wait_sessions--;  
  76.             if (wait_result != 0){  
  77.                 //等待超時,關閉半同步  
  78.                 rpl_semi_sync_master_wait_timeouts++;  
  79.                 switch_off();  
  80.             }else{  
  81.                  wait_time = getWaitTime(start_ts);  
  82.                  if (wait_time < 0){  
  83.                      //表明時鐘錯誤,可能是做了時間調整  
  84.                     rpl_semi_sync_master_timefunc_fails++;  
  85.                  }else{  
  86.                      //將等待事件與該等待計入總數  
  87.                     rpl_semi_sync_master_trx_wait_num++;  
  88.                     rpl_semi_sync_master_trx_wait_time += wait_time;  
  89.                  }  
  90.             }  
  91.         }//end while  
  92. l_end:  
  93.         /* Update the status counter. */  
  94.         if (is_on())  
  95.             rpl_semi_sync_master_yes_transactions++;  
  96.         else  
  97.             rpl_semi_sync_master_no_transactions++;  
  98.               
  99.     }  
  100.     /* Last waiter removes the TranxNode */  
  101.     if (trx_wait_binlog_name && active_tranxs_  
  102.         && entry && entry->n_waiters == 0)  
  103.         active_tranxs_->clear_active_tranx_nodes(trx_wait_binlog_name,  
  104.                                              trx_wait_binlog_pos);  
  105.     THD_EXIT_COND(NULL, & old_stage);  
  106.   
  107. }  
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()函式上,杜塞時間越長,累積的執行緒越多,容易引發雪崩,所以超時時間設定需謹慎,並非隨意設定。

4、參考

http://mp.weixin.qq.com/s?__biz=MzIwNzEzNDkxNQ%3D%3D&idx=1&mid=401148347&scene=21&sn=f3372a773ddf948c5ebe35e77a8e1b3a




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

相關文章