MySQL 5.7中sync_binlog參數(shù)和半同步中after_commit和after_sync的區(qū)別


本文為我的一些零散記錄供以后參考,本來知道已經(jīng)很久了但是有朋友問到老是需要翻很久,這里干脆記錄下來,但是水平有限都不深入,如有誤導請見諒為什么將他們放在一起討論因為他們都存在于同一個函數(shù)MYSQL_BIN_LOG::ordered_commit函數(shù)中。
代碼版本:percona 5.7.14


以下討論sync_binlog參數(shù)在5.7中的作用

一、sync_binlog參數(shù)設置在源碼中的表示

這個參數(shù)大家都知道控制著binlog的刷盤時機,但是在5.7中其還有另外一個功能,我這里將解釋他的兩個功能。我摘取了源碼中說明問題的部分進行展示如下:

  • flush階段:
flush_error= process_flush_stage_queue(&total_bytes, &do_rotate,&wait_queue);//進行binlog的從binlog buffer或者臨時文件寫入到binlog文件(注意是寫到kernel buffer還沒做fsync),同時觸發(fā)innodb的組提交邏輯,innodb組提交的邏輯代碼是阿里的印風兄寫的,我請教過他。
update_binlog_end_pos_after_sync= (get_sync_period() == 1);//sync_binlog參數(shù) 如果為1則為真如果不為1則為假
    if (!update_binlog_end_pos_after_sync)//如果sync_binlog=1則 這里不發(fā)信號給dump 如果不是1則發(fā)信號進行dump
      update_binlog_end_pos();

其中get_sync_period()函數(shù)返回就是sync_binlog的設置,這里能夠清晰看到如果sync_binlog != 1才會 在flush階段發(fā)送信號給dump線程。

  • sync階段
if (flush_error == 0 && total_bytes > 0) //這里進行sync binlog,
  {
    DEBUG_SYNC(thd, "before_sync_binlog_file");
    std::pair<bool, bool> result= sync_binlog_file(false);
    sync_error= result.first;
  }

  if (update_binlog_end_pos_after_sync) //如果sync_binlog = 1 這里才發(fā)送信號給dump線程通知進行發(fā)送binlog
  {
    THD *tmp_thd= final_queue;

    while (tmp_thd->next_to_commit != NULL)
      tmp_thd= tmp_thd->next_to_commit;
    if (flush_error == 0 && sync_error == 0)
      update_binlog_end_pos(tmp_thd->get_trans_pos());
  }

如果sync_binlog = 1 這里才發(fā)送信號給dump線程通知進行發(fā)送binlog。
同時如果我們翻開sync_binlog_file函數(shù)的邏輯會發(fā)現(xiàn)這樣一個邏輯:

if (force || (sync_period && ++sync_counter >= sync_period))
  {
    sync_counter= 0;

很顯然這里有一個計數(shù)器sync_counter,如果當sync_binlog>1的時候才,等到sync_counter大于你設置的sync_binlog的值的時候才會觸發(fā)fsync binlog(注意這里是++sync_counter 先自增再比較),這里也解釋了sync_binlog>1的時候代表的是什么值,代表是組提交的次數(shù)。

二、sync_binlog參數(shù)在5.7中作用的總結(jié)
  • sync_binlog=0:binlog從不FSYNC刷盤,依賴于OS刷盤機制,同時dump線程會在flush階段后進行binlog傳輸
  • sync_binlog=1:binlog每次組提交進行FSYNC刷盤,同時dump線程會在sync階段后進行binlog傳輸
  • sync_binlog>1:binlog將在指定次數(shù)組提交后FSYNC刷盤,同時dump線程會在flush階段后進行binlog傳輸
三、為什么這么修改

這也是一個朋友問我的問題,如果主庫異常重啟后,從庫是否有比主庫多事物的風險,實際上這個問題就是到底在什么階段后dump線程進行傳輸binlog的問題。實際上如果在flush階段過后傳輸確實可能出現(xiàn)這個問題,而在sync階段后傳輸這個時候binlog已經(jīng)落盤了,就不會有這種風險了。如果出現(xiàn)這種錯誤會報錯如下,這個錯誤也是有朋友遇到過的:

ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER 
"Slave has more GTIDs than the master has, using the master's SERVER_UUID. This may indicate that the end of the binary log was truncated or that the last binary log file was lost, e.g., after a power or disk failure when sync_binlog != 1. The master may or may not have rolled back transactions that were already replicated to the slave. Suggest to replicate any transactions that master has rolled back from slave to master, and/or commit empty transactions on master to account for transactions that have been committed on master but are not included in GTID_EXECUTED." 

下面開始討論半同步中after_commit和after_sync的區(qū)別,這個問題一直也是大家討論的重點。

四、從一個問題出發(fā)討論

也是有很多朋友問我,其中一個問題如下:

半同步:after_sync模式
測試:超時時間設置為1個小時不讓主庫切換異步,同時停掉slave的I/O線程,
主庫:
session1:插入一條數(shù)據(jù)hang住
session2:插入一條數(shù)據(jù)hang住
session3:插入一條數(shù)據(jù)hang住
其中session1狀態(tài)為等待ACK,其他session狀態(tài)為query end

問為什么其他session狀態(tài)不是等待ACK而是query end。如果是after_commit模式則全部是等待ACK狀態(tài)

實際上拿到這位朋友的pstack后大概就能確認大概是什么問題如下:

  • 等待ACK線程:
Thread 7 (Thread 0x7f44607aa700 (LWP 24897)):
#0  0x00007f4475b02a5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x00007f44603e35d3 in ReplSemiSyncMaster::commitTrx(char const*, unsigned long long) () from /usr/local/mysql/lib/plugin/semisync_master.so
#2  0x0000000000c8197a in Binlog_storage_delegate::after_sync(THD*, char const*, unsigned long long) ()
#3  0x0000000000edd46b in call_after_sync_hook(THD*) ()
#4  0x0000000000eed935 in MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool) ()
#5  0x0000000000eedf55 in MYSQL_BIN_LOG::commit(THD*, bool) ()
#6  0x000000000081e494 in ha_commit_trans(THD*, bool, bool) ()
#7  0x0000000000dce032 in trans_commit_stmt(THD*) ()
#8  0x0000000000d134e7 in mysql_execute_command(THD*, bool) ()
  • 等待LOCK_commit mutex線程
Thread 6 (Thread 0x7f4460769700 (LWP 25017)):
#0  0x00007f4475b05334 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007f4475b0060e in _L_lock_995 () from /lib64/libpthread.so.0
#2  0x00007f4475b00576 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000eed31f in MYSQL_BIN_LOG::change_stage(THD*, Stage_manager::StageID, THD*, st_mysql_mutex*, st_mysql_mutex*) ()
#4  0x0000000000eed5e8 in MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool) ()
#5  0x0000000000eedf55 in MYSQL_BIN_LOG::commit(THD*, bool) ()
#6  0x000000000081e494 in ha_commit_trans(THD*, bool, bool) ()
#7  0x0000000000dce032 in trans_commit_stmt(THD*) ()

這里就很明顯其他提交事物(這里指的是 Thread 6)堵塞在了MYSQL_BIN_LOG::change_stage函數(shù)上,其作用正是獲取某個階段的Mutex。而本線程(這里指的是 Thread 7)則是在ReplSemiSyncMaster::commitTrx上堵塞在某個Mutex上。

五、after_commit和after_sync的代碼位置和區(qū)別

這里直接用代碼說明進行給出,當然我只是提取了說明問題的代碼片段:

  • commit階段:
1、 change_stage(thd, Stage_manager::COMMIT_STAGE,final_queue, leave_mutex_before_commit_stage,&LOCK_commit))//持有LOCK_commit mutext進入commit階段
2、 sync_error= call_after_sync_hook(commit_queue);//這里調(diào)用after sync hook 其在LOCK_commit保護下 此時還沒有做引擎層commit
3、 process_commit_stage_queue(thd, commit_queue);//進行引擎層提交操作,具體細節(jié)以后在研究
4、 mysql_mutex_unlock(&LOCK_commit);//這里提交完成解鎖隊列
5、 stage_manager.signal_done(final_queue); //這里喚醒全部本組堵塞在 flush階段的follower線程 分別做提交 但是如果是order commit 提交已經(jīng)做完 這里什么都不需要做了
6、 (void) finish_commit(thd); //finish_commit會調(diào)用 atfer commit hook 其不在LOCK_commit保護下

如果拋開代碼總結(jié)如下:

  • 1、leader 持有LOCK_commit 鎖 進入 commit階段。
  • 2、如果是設置after_sync,使用after sync 掛鉤來確認ack 。
  • 3、進行引擎層提交,完成后解鎖LOCK_commit 鎖。
  • 4、喚醒所有 follwer線程。
  • 5、如果設置是after_commit,使用after commit 掛鉤來確認ack 。

這里我們可以清楚的看到,他們的區(qū)別,實際上正如其名字一樣就是說到底在那個步驟進行日志傳輸完成的確認,是在實際引擎層提交之前還是之后,如果是在之前則在mutex LOCK_commit的保護下,如果是在之后則不需要持有LOCK_commit mutex,這也是為什么會出現(xiàn)上面那個堵塞案例的原因。在5.7中默認是after_sync設置為after_sync后顯然更加安全,如果是after_commit極端情況下可能引擎層已經(jīng)提交完成,事物對主庫可見,但是從庫還沒有傳輸完成如果從庫奔潰可能出現(xiàn)少事物的情況。


結(jié)語

對于5.7中安全的設置應該盡量保證sync_binlog=1同時設置rpl_semi_sync_master_wait_point為after_sync,這實際上都是默認設置。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容