基於binlog的mysql主從同步

binlog

mysql爲了保證事務的ACID(atomicity,consistency,isolation,durability),用了幾種日誌做配合處理,分別爲binglog(二進制日誌)、redolog(重做日誌)、undolog(回滾日誌)。

重做日誌(redo log)

確保事務的持久性。防止在發生故障的時間點,尚有髒頁未寫入磁盤,在重啓mysql服務的時候,根據redo log進行重做,從而達到事務的持久性這一特性。

回滾日誌(undo log)

保存了事務發生之前的數據的一個版本,可以用於回滾,同時可以提供多版本併發控制下的讀(MVCC),也即非鎖定讀

二進制日誌(binlog):

用於複製,在主從複製中,從庫利用主庫上的binlog進行重播,實現主從同步。 用於數據庫的基於時間點的還原。

我們這裏的重點,就是binlog了。

同步機制

通過圖我們可以看出,binlog同步分爲6個步驟:

  1. master開啓binlog日誌(數據改變會產生日誌)
  2. slave連接mater,開啓同步(前提,同名db必須存在,假如數據不爲空,已有數據必須一致)
  3. master數據變化產生binglog,通過binglog dump線程通知給slave.
  4. slave同步寫入relay log。
  5. slave執行收到的sql日誌(此時已經執行完畢)。
  6. slave執行寫本地binlog。

binlog同步分爲3種同步模式:

  1. STATEMENT模式(SBR)

     每一條會修改數據的sql語句會記錄到binlog中。優點是並不需要記錄每一條sql語句和每一行的數據變化,減少了binlog日誌量,節約IO,提高性能。缺點是在某些情況下會導致master-slave中的數據不一致(如sleep()函數, last_insert_id(),以及user-defined functions(udf)等會出現問題)
    
  2. ROW模式(RBR)

     不記錄每條sql語句的上下文信息,僅需記錄哪條數據被修改了,修改成什麼樣了。而且不會出現某些特定情況下的存儲過程、或function、或trigger的調用和觸發無法被正確複製的問題。缺點是會產生大量的日誌,尤其是alter table的時候會讓日誌暴漲。
    
  3. MIXED模式(MBR)

     以上兩種模式的混合使用,一般的複製使用STATEMENT模式保存binlog,對於STATEMENT模式無法複製的操作使用ROW模式保存binlog,MySQL會根據執行的SQL語句選擇日誌保存方式。
    

步驟

master開啓binlog

binlog_format           = MIXED                         //binlog日誌格式,mysql默認採用statement,建議使用mixed
log-bin                 = /data/mysql/mysql-bin.log     //binlog日誌文件
expire_logs_days        = 7                             //binlog過期清理時間
max_binlog_size         = 100m                          //binlog每個日誌文件大小
binlog_cache_size       = 4m                            //binlog緩存大小
max_binlog_cache_size   = 512m                          //最大binlog緩存大小

slave連接

server-id=2
master-host=xxx
master-user=username
master-password=xxxx
master-port=xxxx
replicate-do-db=dbname
......

# 開啓中繼日誌
relay-log=relay-log
relay-log-index=/data/mysql/relay-log.index
server-id=2
innodb_file_per_table=ON #innodb_file_per_table=ON的情況下,默認創建出來的ibd文件的格式是Barracuda,在這個文件格式下innodb數據行的格式就可以設置爲compressed 或 dynamic 格式了。compressed 提供壓縮功能節約空間,dynamic能優化對blob,text這樣的數據類型的存儲以提升性能。5.7+才支持
skip_name_resolve=ON  #跳過主機檢測

binlog dump theard

主庫在接收到從庫發送的COM_BINLOG_DUMP_GTID命令後,會調用com_binlog_dump_gtid函數處理從庫拉取binlog的請求。主要的執行邏輯如下:

  1. 獲取從庫發送的binlog相關信息,包括server_id,binlog名稱,binlog位置,binlog大小,gtid信息等等。
  2. 檢查是否已經存在與該從庫關聯的binlog dump線程,如果存在,結束該binlog dump線程。爲什麼會已經存在binlog dump線程?在某些場景下,比如從庫io線程停止,這時主庫的binlog dump線程正好在等待binlog更新,即等待主庫寫入數據,如果主庫一直沒有寫入數據,dump線程就會等待很長時間,如果這時從庫io線程重連到主庫,就會發現主庫已經存在與該從庫對應的dump線程。所以主庫在處理從庫binlog dump請求時,先檢查是否已經存在dump線程。
  3. 調用mysql_binlog_send函數,向從庫發送binlog。這個函數內部實際是通過一個C++類Binlog_sender來實現的,該類在源碼文件sql/rpl_binlog_sender.h中定義,調用該類的run成員函數來發送binlog。
  4. Binlog_sender類的run成員函數,主要邏輯是通過多個while嵌套循環,依次讀取binlog文件,binlog文件中的event,將event發送給從庫。如果event已經在從庫中應用,則忽略該event。當讀到最新的binlog時,如果所有event都已經發送完成,那麼線程會等待binlog更新事件update_cond,有新的binlog event寫入後,會廣播通知所有等待update_cond事件的線程開始工作,也包括dump線程。dump線程在等待update_cond事件時有一個超時時間,這個時間就是master_heartbeat_period,即主庫dump線程與從庫io線程的心跳時間間隔,這個值在從庫執行change master 時設置,啓動io線程時把該值傳遞給主庫,主庫dump線程等待update_cond超時後,將會給從庫發送一個heartbeat event,之後會繼續等待update_cond事件。上述過程會一直循環,直到dump線程被kill或者遇到其他錯誤。
  5. 當執行邏輯從Binlog_sender類內部的while循環退出,緊接着會調用unregister_slave函數註銷從庫的註冊。這個時候在主庫上執行show slave hosts,就會發現從庫的信息已經沒有了。

slave relaylog

relay-log的結構和binlog非常相似,只不過他多了一個master.info和relay-log.info的文件。

master.info記錄了上一次讀取到master同步過來的binlog的位置,以及連接master和啓動複製必須的所有信息。

relay-log.info記錄了文件複製的進度,下一個事件從什麼位置開始,由sql線程負責更新。

relay-log無法自動刪除

背景:

在運維一個mysql實例時,發現其數據目錄下的relay-log 長期沒有刪除,已經堆積了幾十個relay-log。 然而其他作爲Slave服務器實例卻沒有這種情況。

現象分析:

通過收集到的信息,綜合分析後發現relay-log無法自動刪除和以下原因有關。

  • 該實例原先是一個Slave:導致relay-log 和 relay-log.index的存在
  • 該實例目前已經不是Slave:由於沒有了IO-Thread,導致relay-log-purge 沒有起作用( 這也是其他Slave實例沒有這種情況的原因,因爲IO-thread會做自動rotate操作)。
  • 該實例每天會進行日常備份:Flush logs的存在,導致每天會生成一個relay-log
  • 該實例沒有配置expire-logs-days:導致flush logs時,也不會做relay-log清除
  • 簡而言之就是: 一個實例如果之前是Slave,而之後停用了(stop slave),且沒有配置expire-logs-days的情況下,會出現relay-log堆積的情況。

Binary Log rotate機制:

  • Rotate:每一條binary log寫入完成後,都會判斷當前文件是否超過max_binlog_size ,如果超過則自動生成一個binlog file
  • Delete:expire-logs-days 只在 實例啓動時 和 flush logs 時判斷,如果文件訪問時間早於設定值,則purge file

Relay Log rotate 機制:

  • Rotate:每從Master fetch一個events後,判斷當前文件是否超過max_relay_log_size 如果超過則自動生成一個新的relay-log-file
  • Delete: purge-relay-log 在SQL Thread每執行完一個events時判斷,如果該relay-log 已經不再需要則自動刪除
  • Delete: expire-logs-days 只在 實例啓動時 和 flush logs 時判斷,如果文件訪問時間早於設定值,則purge file (同Binlog file) (updated: expire-logs-days和relaylog的purge沒有關係)
因此還是建議配置 expire-logs-days , 否則當我們的外部腳本因意外而停止時,還能有一層保障。
因此建議當slave不再使用時,通過reset slave來取消relaylog,以免出現relay-log堆積的情況。

salve sql exe

這個執行步驟和一般執行sql沒有太大區別

slave wirte binlog

這個執行步驟和master一致

各種同步模式的優缺點

SBR 的優點:

歷史悠久,技術成熟:

  • binlog文件較小
  • binlog中包含了所有數據庫更改信息,可以據此來審覈數據庫的安全等情況
  • binlog可以用於實時的還原,而不僅僅用於複製

主從版本可以不一樣,從服務器版本可以比主服務器版本高

SBR 的缺點:

不是所有的UPDATE語句都能被複制,尤其是包含不確定操作的時候。調用具有不確定因素的 UDF 時複製也可能出問題,使用以下函數的語句也無法被複制:

  • LOAD_FILE()
  • UUID()
  • USER()
  • FOUND_ROWS()
  • SYSDATE() (除非啓動時啓用了 --sysdate-is-now 選項)

INSERT ... SELECT 會產生比 RBR 更多的行級鎖

複製需要進行全表掃描(WHERE 語句中沒有使用到索引)的 UPDATE 時,需要比 RBR 請求更多的行級鎖。對於有 AUTO_INCREMENT 字段的 InnoDB表而言,INSERT 語句會阻塞其他 INSERT 語句。對於一些複雜的語句,在從服務器上的耗資源情況會更嚴重,而 RBR 模式下,只會對那個發生變化的記錄產生影響。存儲函數(不是存儲過程)在被調用的同時也會執行一次 NOW() 函數,這個可以說是壞事也可能是好事,確定了的 UDF 也需要在從服務器上執行,數據表必須幾乎和主服務器保持一致纔行,否則可能會導致複製出錯,執行復雜語句如果出錯的話,會消耗更多資源

RBR 的優點:

任何情況都可以被複制,這對複製來說是最安全可靠的,和其他大多數數據庫系統的複製技術一樣,多數情況下,從服務器上的表如果有主鍵的話,複製就會快了很多,複製以下幾種語句時的行鎖更少:

  • INSERT ... SELECT
  • 包含 AUTO_INCREMENT 字段的 INSERT
  • 沒有附帶條件或者並沒有修改很多記錄的 UPDATE 或 DELETE 語句

執行 INSERT,UPDATE,DELETE 語句時鎖更少,從服務器上採用多線程來執行復製成爲可能

RBR 的缺點:

binlog 大了很多,複雜的回滾時 binlog 中會包含大量的數據,主服務器上執行 UPDATE 語句時,所有發生變化的記錄都會寫到 binlog 中,而 SBR 只會寫一次,這會導致頻繁發生 binlog 的併發寫問題,UDF 產生的大 BLOB 值會導致複製變慢,無法從 binlog 中看到都複製了寫什麼語句,當在非事務表上執行一段堆積的SQL語句時,最好採用 SBR 模式,否則很容易導致主從服務器的數據不一致情況發生

另外,針對系統庫 mysql 裏面的表發生變化時的處理規則如下:
如果是採用 INSERT,UPDATE,DELETE 直接操作表的情況,則日誌格式根據 binlog_format 的設定而記錄
如果是採用 GRANT,REVOKE,SET PASSWORD 等管理語句來做的話,那麼無論如何都採用 SBR 模式記錄
注:採用 RBR 模式後,能解決很多原先出現的主鍵重複問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章