1. 程式人生 > >從MySQL 5.5到5.7看複製的演進

從MySQL 5.5到5.7看複製的演進

概要:MySQL 5.5 支援單執行緒模式複製,MySQL 5.6 支援庫級別的並行複製,MySQL 5.7 支援事務級別並行複製。結合這個主線我們可以來分析一下MySQL以及社群發展的一個前因後果。

MySQL5.5,對於複製我們可以這樣理解:主庫有個 dump binlog thread 不停的 dump binlog,然後以event為單位傳送給從庫 的 iothread,iothread 收到主庫傳過來的event寫入relaylog ,隨後sql_thread 讀取relaylog 對這些event以事務為單位進行回放。

那麼對於MySQL 5.5這個版本,在我們的使用過程中遇到那些問題,或者有那些不便呢?

  • 首先DB壓力偏大時,從庫帶來的延遲較大,影響只讀業務

    由於新硬體的發展,SSD的引入和多core的CPU,master節點的併發處理能力持續提升,slave節點完全按照binlog寫入順序的單執行緒回放,已完全跟不上master節點的吞吐能力。

    在不考慮主從硬體配置差異情況下,延遲大的其根本原因在於:Master壓力過大,而Slave是單執行緒回放日誌。那麼要解決這個問題,從技術上來說可以把單執行緒變為多執行緒,利用並行帶來的優勢;從業務上來說可以進行拆庫,把一些業務線或者功能模組獨立出去;更進一步我們可以拆表,把壓力分擔到多個Master上去。

    1. 假如我們在不變動業務的情況下,從技術面來解決這個問題有哪些方向呢:

      • 社群的解決方案:阿里開源的canal,基於表級別並行同步,可以減小同步延遲時間

      • 官方的解決方案:在2011年10月份釋出了一個里程碑版本基於schema級別的並行複製[MySQL5.6.3 (multi-threaded slave)],以及基於group Commit的 MySQL5.7版本,最大化還原主庫並行度。

      MySQL5.6,對於複製我們可以這樣理解,主庫有個 dump binlog thread 不停的 dump binlog,然後以event為單位傳送給從庫 的 iothread,io

      thread 收到主庫傳過來的event寫入relaylog。【隨後的事情和MySQL5.5就發生了一些變化】,由coordinator執行緒來讀取relaylog,然後根據不同的db以事務為單位分配到不同的work執行緒。如果binlog row event操作的是不同的schema的物件,在確定沒有DDL和foreign key依賴的情況下,就可以實現並行複製。

      MySQL5.7可以說是最大還原了主庫上的並行,在基於Group Commit的基礎上,所有在主庫上能夠完成prepared的語句表示沒有資料衝突,分配成相同的lastcommitted,就可以在slave節點並行複製。 那麼它是如何識別那些事務是一起提交的呢?其實就是在gtid event 中增加了兩個欄位【int64 lastcommitted;int64 sequencenumber】,當slave的coordinator執行緒在分發這些event的時候,具有相同lastcommitted 的事務(event的集合)就可以同時傳送給不同的work執行緒,達到並行同步的目的。

      小結:就並行複製,按粒度區分有三種策略,粒度從粗到細是按庫、按表、按行。 這三個的對比中,並行度越來越大,額外損耗也是。無關大事務不會影響併發度。按照commit_id 的策略,適用範圍更廣,額外消耗也低。5.7的改進策略併發性更優。但出現大事務會拖後腿。

    2. 那麼我們只有一例項只有一個database,這種情況下我們就只有拆庫拆表了:

      對於這種情況下,我們可以選擇在應用層做分庫分表,也可以選擇搞個中間層。不同的方案有不同的優劣。

      • 應用層具有較好的效能,但是程式碼耦合在業務,如果後續擴容還需該程式碼,不能做到平滑擴容拆分,假如有多個業務都需要實現同樣的功能,那麼會帶來重複的工作量,而且工作難度也上升一個臺階。

      • 中介軟體層具有較好的擴充套件性,低耦合性,如果DB擴容拆分,應用可以做到無感知,無改動。那麼也有一些成熟的開源方案,比如MyCAT,Cobar,Atlas,kingshard等。

  • 其次主從切換時帶來的複雜度較大,需要計算position或者重做從庫

    一般情況下我們的MySQL都是一主多從架構,這樣既能給我們提供讀寫分離、負載均衡的便利,也能給我們提供容災的能力。但是假如我們的主庫掛掉,這時我們會把從庫提升為主庫,但是在把從庫提升為新主的時候帶來了架構的微變化。為了還能利用以上便利、提供容災能力我們還得重新構建這個新主的多個從庫。此時問題就來了,我們從庫必須知道我當前應該從Master 的那個位置開始複製,也就是說必須拿到Master的position 。為了拿到這個位置我們有兩種辦法,一種簡單粗暴,重做Slave;另一種是通過一些列複雜計算、補回差異資料,算出當前資料和新主資料的差異點,從而得到新主庫position,導致HA切換和資料保護帶來巨大的挑戰。

    • MMM架構(Master-Master replication manager for MySQL)

      MMM是一套支援雙主故障切換和雙主日常管理的指令碼程式,可以再主庫故障時保證熱備切換為新主庫,並且自動的將從庫指向新主。但是這個架構本身不能保證資料的一致性。

    • MHA架構(Master High Availability)

      MHA目前在MySQL高可用方面是一個相對成熟的解決方案,在自動進行故障切換的過程中,能最大程度上保證資料的一致性,以達到真正意義上的高可用。

      那麼HMA是如何最大程度保證資料一致的呢?當主庫down掉時,MHA試圖從宕機的主伺服器上儲存二進位制日誌,最大程度的保證資料的不丟失,但這並不總是可行的。如果主庫傳送down機,日誌會出現不同程度的丟失,有個解決辦法就是設定半同步複製。MHA在把從提升為主的過程中,會進行一系列日誌對比,找到最接近主庫的從庫提升為新主庫,把從庫間差異化的資料拿出來進行應用等等。

    • GTID (Global Transaction ID)

      在MySQL 5.6 以後官方引入了GTID,即在整個叢集內部,每個事務都有全域性唯一的一個標識,這樣一來,當我們主庫傳送down掉,或者MySQL架構有調整的時候,我們就不用很頭疼的去計算position;或者去配置略為複雜的MHA。我們只需要輕輕鬆鬆敲個CHANGE MASTER 命令帶上AUTO_POSITION就可以了,然後關於MASTER該從哪個binlog開始推送event給Slave這個完全由MySQL來幫我們計算。這個真是DBA們的福音啊。

      簡單看看,為什麼這麼GTID這麼神奇吧。在MySQL內部幫我們記錄著 gtidpurged 和gtidexecuted 兩個集合。顧名思義,gtidexecuted 代表的時當前已經執行過的GTID的集合;一般情況下我們binlog不可能永久儲存,那麼gtidpurged代表的就是當前binlog已經沒有的GTID集合,它是gtidexecuted的子集。我們知道在事務是不能跨binlog存在的,意味著每個binlog都會有一個完整的事務集合,同樣每個binlog檔案的 header 部分,也都存放著這個binlog以前的 gtidexecuted 集合。我們的Slave 在應用Binlog的時候都會記錄自己當前已經執行過的最後一個事務GTID,那麼我們在切換主庫的時候,Slave就會把這個ID給帶上,然後Master端就會拿到這個GTID和自己當前的gtidexecuted、gtidpurged 集合進行對比,從而給到Slave一個合理的解釋。

OK,到這裡MySQL從5.5的單執行緒複製,到5.6基於Schema級別的複製,再到5.7最大化還原主庫的並行就接近尾聲了。同時在這期間我們還給出了一些社群上、或者非技術上的解決方案。