mysql複製簡單介紹了mysql semi-sync的出現的原因,並說明瞭semi-sync如何保證不丟數據。這篇文章主要側重於semi-sync的實現,結合源碼將semi-sync的實現過程展現給大家。最新的semi-sync源碼可以參考官方5.7版本的實現,https://github....
mysql複製簡單介紹了mysql semi-sync的出現的原因,並說明瞭semi-sync如何保證不丟數據。這篇文章主要側重於semi-sync的實現,結合源碼將semi-sync的實現過程展現給大家。最新的semi-sync源碼可以參考官方5.7版本的實現,https://github.com/mysql/mysql-server。
打開semi-sync的正確姿勢
預設情況下的mysql複製都是非同步複製,mysql通過參數來控制semi-sync開關。具體而言,主庫上通過rpl_semi_sync_master_enabled參數控制,備庫上通過rpl_semi_sync_slave_enabled參數控制,打開這兩個參數後,mysql semi-sync的特性就打開了。註意對於備庫而言,為了保證半同步立即生效,需要重啟slave的IO線程。另外,還有一個比較重要的參數是rpl_semi_sync_master_timeout,這個參數用於控制master等待semi-slave ack報文的時間,單位是毫秒,預設是10000。master等待超時,則切換為普通的非同步複製。
master:
set global rpl_semi_sync_master_enabled=1;
set global rpl_semi_sync_master_timeout=xxx;
slave:
stop slave io_thread;
set global rpl_semi_sync_slave_enabled=1;
start slave io_thread;
另外需要註意的是,打開了上述兩個參數只說明master-slave已經具備打開semi-sync的基本條件了,但複製是否依照半同步運行,還需要根據Rpl_semi_sync_master_status的狀態值確定。因為比如slave較master有很大延遲(超過rpl_semi_sync_master_timeout),那麼複製切換為普通複製。對於需要調試代碼的童鞋而言,rpl_semi_sync_master_trace_level和rpl_semi_sync_slave_trace_level非常重要,通過設置level取值,可以列印日誌記錄半同步的詳細過程,方便定位問題。
semi-sync的實現
semi-sync說到底也是一種複製,只不過是在普通的複製基礎上,添加了一些步驟來實現。因此semi-sync並沒有改變複製的基本框架,我們的討論也從master,slave兩方面展開。
1.master(主庫)
主庫上面主要包含三個部分,(1).負責與slave-io線程對接的binlog dump線程,將binlog發送到slave,(2).主庫上寫事務的工作線程,(3).收取semi-slave報文的ack_receiver線程。
(1).binlog dump流程
主要執行邏輯在mysql_binlog_send函數中。
1.判斷slave是否是semi_slave,調用add_slave將semi-slave加入到ack_receiver線程的監聽隊列中。判斷的邏輯是slave對應的會話上是否設置了參數rpl_semi_sync_slave。
2.根據slave的請求偏移和binlog文件,從指定位點讀取binlog
3.根據文件和位點,撈binlog文件中的數據
4.調用updateSyncHeader設置數據包頭semi-sync標記
根據實時semi-sync運行狀態來確定是否設置(這個狀態由ack_receiver線程根據是否及時收到slave-ack報文設置)
5.調用my_net_write發送binlog
6.調用net_flush確認網路包發送出去
如果當前所有產生的binlog已經處理完,需調用wait_for_update_bin_log等待binlog更新。
(2).半同步事務提交流程
mysql5.6以後提交採用組提交方式,主要分為三個階段,每個階段有一個隊列,增加semi-sync後,又增加了一個semi-sync階段。
1.flush階段:
隊列能保證寫binlog的順序與innodb-commit的順序一致。通過隊列,可以保證順序寫每個事務的binlog-cache,然後只進行一次write操作(flush_cache_to_file)。flush階段後,如果sync_binlog不是1,則通知master有新binlog產生;如果sync_binlog為1,則等待sync階段後,再通知dump線程有新binlog產生。這裡我理解是因為如果不為1,則可能沒有後續的sync階段,而操作系統緩存也有binlog數據,所以可以在flush階段後通知;而對於sync_binlog為1的情況,可以保證主庫的binlog先落地,永遠比備庫多。但如果sync_binlog不為1,比如1000,則主機異常情況下,則可能出現備庫的binlog比主庫還多的情況。根據sync_binlog的設置,確認是否要跳過sync階段。
2.sync階段:
sync_binlog_file
3.semi_sync階段:
call_after_sync,等待備庫應答。調用cond_timewait等待條件變數&COND_binlog_send_
4.commit階段:
innodb-commit
waitAfterCommit,等待備庫應答。調用cond_timewait等待條件變數&COND_binlog_send_ 。最終我們根據semi-sync複製模式的設置(AFTER_COMMIT,AFTER_SYNC),來確定是第(3)步還是第(4)步進行等待。
(3).接收slave-ack報文流程
這個流程的工作主要在ack_receiver線程中,這個線程的主要作用是監聽semi-slave的ack包,確認master-slave鏈路是否工作在半同步狀態,並根據實際運行狀態將普通複製與半同步複製進行切換。打開主庫rpl_semi_sync_master_enabled參數後,該線程啟動,關閉參數後,該線程消亡。
流程如下:
1.遍歷semi-slave數組
2.通過select函數監聽每個slave是否有網路包過來
3.調用my_net_read讀取包數據
4.調用reportReplyPacket處理semi-sync複製狀態
若備庫已經獲取了最新的binlog位點,則喚醒等待的工作線程
5.調用reportReplyBinlog喚醒等待的線程,mysql_cond_broadcast(&COND_binlog_send_);
2.slave(備庫)
主要實現在(handle_slave_io)
1.啟動io-thread後,調用safe_connect建立與master的連接
2.調用request_dump函數處理請求binlog邏輯
(1).執行命令SET @rpl_semi_sync_slave= 1,設置一個局部變數,通過這個參數標記slave為semi-slave
(2).發送命令COM_BINLOG_DUMP請求日誌
迴圈從master端收取日誌,處理日誌
{
1.調用read_event,從master端收取日誌(如果沒有網路包,會阻塞等待)
2.調用slaveReadSyncHeader,確定網路包頭是否有semi-sync標記
3.調用queue_event將日誌寫入relay-log,在這個過程中會過濾自身server-id的日誌
4.如果有semi-sync標記,調用slaveReply函數,發送ack報文
}