MySQL主從複製原理剖析與應用實踐

2023-04-11 12:01:34

vivo 網際網路伺服器團隊- Shang Yongxing

MySQL Replication(主從複製)是指資料變化可以從一個MySQL Server被複制到另一個或多個MySQL Server上,通過複製的功能,可以在單點服務的基礎上擴充資料庫的高可用性、可延伸性等。

一、背景

MySQL在生產環境中被廣泛地應用,大量的應用和服務都對MySQL服務存在重要的依賴關係,可以說如果資料層的MySQL範例發生故障,在不具備可靠降級策略的背景下就會直接引發上層業務,甚至使用者使用的障礙;同時MySQL中儲存的資料也是需要儘可能地減少丟失的風險,以避免故障時出現資料丟失引發的資產損失、客訴等影響。

在這樣對服務可用性和資料可靠性需求的背景下,MySQL在Server層提供了一種可靠的基於紀錄檔的複製能力(MySQL Replication),在這一機制的作用下,可以輕易構建一個或者多個從庫,提高資料庫的高可用性可延伸性,同時實現負載均衡

  • 實時資料變化備份

  • 主庫的寫入資料會持續地在冗餘的從庫節點上被執行保留,減少資料丟失的風險

  • 橫向拓展節點,支撐讀寫分離

  • 當主庫本身承受壓力較大時,可以將讀流量分散到其它的從庫節點上,達成讀擴充套件性和負載均衡

  • 高可用性保障

  • 當主庫發生故障時,可以快速的切到其某一個從庫,並將該從庫提升為主庫,因為資料都一樣,所以不會影響系統的執行

具備包括但不限於以上特性的MySQL叢集就可以覆蓋絕大多數應用和故障場景,具備較高的可用性與資料可靠性,當前儲存組提供的生產環境MySQL就是基於預設的非同步主從複製的叢集,向業務保證可用性99.99%,資料可靠性99.9999%的線上資料庫服務。

本文將深入探討MySQL的複製機制實現的方式, 同時討論如何具體地應用複製的能力來提升資料庫的可用性,可靠性等。

二、複製的原理

2.1 Binlog 的引入

從比較寬泛的角度來探討複製的原理,MySQL的Server之間通過二進位制紀錄檔來實現實時資料變化的傳輸複製,這裡的二進位制紀錄檔是屬於MySQL伺服器的紀錄檔,記錄了所有對MySQL所做的更改。這種複製模式也可以根據具體資料的特性分為三種:

  • Statement:基於語句格式

  • Statement模式下,複製過程中向獲取資料的從庫傳送的就是在主庫上執行的SQL原句,主庫會將執行的SQL原有傳送到從庫中。

  • Row:基於行格式

  • Row模式下,主庫會將每次DML操作引發的資料具體行變化記錄在Binlog中並複製到從庫上,從庫根據行的變更記錄來對應地修改資料,但DDL型別的操作依然是以Statement的格式記錄。

  • Mixed:基於混合語句和行格式

  • MySQL 會根據執行的每一條具體的 SQL 語句來區分對待記錄的紀錄檔形式,也就是在 statement 和 row 之間選擇一種。

最早的實現是基於語句格式,在3.23版本被引入MySQL,從最初起就是MySQL Server層的能力,這一點與具體使用的儲存引擎沒有關聯;在5.1版本後開始支援基於行格式的複製;在5.1.8版本後開始支援混合格式的複製。

這三種模式各有優劣,相對來說,基於Row的行格式被應用的更廣泛,雖然這種模式下對資源的開銷會偏大,但資料變化的準確性以及可靠性是要強於Statement格式的,同時這種模式下的Binlog提供了完整的資料變更資訊,可以使其應用不被侷限在MySQL叢集系統內,可以被例如Binlogserver,DTS資料傳輸等服務應用,提供靈活的跨系統資料傳輸能力, 目前網際網路業務的線上MySQL叢集全部都是基於Row行格式的Binlog

2.2  Binlog 的要點

2.2.1 Binlog事件型別

對於Binlog的定義而言,可以認為是一個個單一的Event組成的序列,這些單獨的Event可以主要分為以下幾類:

圖片

各類Event出現是具有顯著的規律的:

  • XID_EVENT標誌一個事務的結尾

  • 當發生了DDL型別的QUERY_EVENT,那麼也是一次事務的結束提交點,且不會出現XID_EVENT

  • GTID_EVENT只有開啟了GTID_MODE(MySQL版本大於5.6)

  • TABLE_MAP_EVENT必定出現在某個表的變更資料前,存在一對多個ROW_EVENT的情況

除了上面和資料更貼近的事件型別外,還有ROTATE_EVENT(標識Binlog檔案發生了切分),FORMAT_DESCRIPTION_EVENT(定義後設資料格式)等。

2.2.2 Binlog的生命週期

Binlog和Innodb Log(redolog)的存在方式是不同的,它並不會輪轉重複覆寫檔案,Server會根據設定的單個Binlog檔案大小設定不斷地切分併產生新的Binlog,在一個.index檔案記錄當前硬碟上所有的binlog檔名,同時根據Binlog過期時間回收刪除掉過期的Binlog檔案,這兩個在目前自建資料庫的設定為單個大小1G,保留7天。

所以這種機制背景下,只能在短期內追溯歷史資料的狀態,而不可能完整追溯資料庫的資料變化的,除非是還沒有發生過紀錄檔過期回收的Server。 

2.2.3 Binlog事件範例

Binlog是對Server層生效的,即使沒有從庫正在複製主庫,只要在設定中開啟了log_bin,就會在對應的本地目錄儲存binlog檔案,使用mysqlbinlog開啟一個Row格式的範例binlog檔案:

圖片

如上圖,可以很明顯地注意到三個操作,建立資料庫test, 建立資料表test, 一次寫入引發的行變更,可讀語句(create, alter, drop, begin, commit.....)都可以認為是QUERY_EVENT,而Write_rows就屬於ROW_EVENT中的一種。

在複製的過程中,就是這樣的Binlog資料通過建立的連線傳送到從庫,等待從庫處理並應用。

2.2.4 複製基準值

Binlog在產生時是嚴格有序的,但它本身只具備秒級的物理時間戳,所以依賴時間進行定位或排序是不可靠的,同一秒可能有成百上千的事件,同時對於複製節點而言,也需要有效可靠的記錄值來定位Binlog中的水位,MySQL Binlog支援兩種形式的複製基準值,分別是傳統的Binlog File:Binlog Position模式,以及5.6版本後可用的全域性事務序號GTID。

  • FILE Position

只要開啟了log_bin,MySQL就會具有File Position的位點記錄,這一點不受GTID影響。

File: binlog.000001
Position: 381808617

這個概念相對來說更直觀,可以直接理解為當前處在File對應編號的Binlog檔案中,同時已經產生了合計Position bytes的資料,如例子中所示即該範例已經產生了381808617 bytes的Binlog,這個值在對應機器直接檢視檔案的大小也是匹配的,所以File Postion就是檔案序列與大小的對應值。

基於這種模式開啟複製,需要顯式地在複製關係中指定對應的File和Position:

CHANGE MASTER TO MASTER_LOG_FILE='binlog.000001', MASTER_LOG_POSITION=381808617;

這個值必須要準確,因為這種模式下從庫獲取的資料完全取決於有效的開啟點,那麼如果存在偏差,就會丟失或執行重複資料導致複製中斷。

  • GTID

MySQL 會在開啟GTID_MODE=ON的狀態下,為每一個事務分配唯一的全域性事務ID,格式為:server_uuid:id

Executed_Gtid_Set: e2e0a733-3478-11eb-90fe-b4055d009f6c:1-753

其中e2e0a733-3478-11eb-90fe-b4055d009f6c用於唯一地標識產生該Binlog事件的範例,1-753表示已經產生或接收了由e2e0a733-3478-11eb-90fe-b4055d009f6c範例產生的753個事務;

從庫在從主庫獲取Binlog Event時,自身的執行記錄會保持和獲取的主庫Binlog GTID記錄一致,還是以e2e0a733-3478-11eb-90fe-b4055d009f6c:1-753,如果有從庫對e2e0a733-3478-11eb-90fe-b4055d009f6c開啟了複製,那麼在從庫自身執行show master status也是會看到相同的值。

如果說從庫上可以看到和複製的主庫不一致的值,那麼可以認為是存在errant GTID,這個一般是由於主從切換或強制在從庫上執行了寫操作引發,正常情況下從庫的Binlog GTID應該和主庫的保持一致;

基於這種模式開啟複製,不需要像File Position一樣指定具體的值,只需要設定:

CHANGE MASTER TO MASTER_AUTO_POSITION=1;

從庫在讀取到Binlog後,會自動根據自身Executed_GTID_Set記錄比對是否存在已執行或未執行的Binlog事務,並做對應的忽略和執行操作。

2.3 複製的具體流程

2.3.1 基本複製流程

當主庫已經開啟了binlog( log_bin = ON ),並正常地記錄binlog,如何開啟複製?

這裡以MySQL預設的非同步複製模式進行介紹:

  1. 首先從庫啟動I/O執行緒,跟主庫建立使用者端連線。

  2. 主庫啟動binlog dump執行緒,讀取主庫上的binlog event傳送給從庫的I/O執行緒,I/O執行緒獲取到binlog event之後將其寫入到自己的Relay Log中。

  3. 從庫啟動SQL執行緒,將等待Relay中的資料進行重放,完成從庫的資料更新。

總結來說,主庫上只會有一個執行緒,而從庫上則會有兩個執行緒。

圖片

 

  • 時序關係

當叢集進入執行的狀態時,從庫會持續地從主庫接收到Binlog事件,並做對應的處理,那麼這個過程中將會按照下述的資料流轉方式:

  1. Master將資料更改記錄在Binlog中,BinlogDump Thread接到寫入請求後,讀取對應的Binlog

  2. Binlog資訊推播給Slave的I/O Thread。

  3. Slave的I/O 執行緒將讀取到的Binlog資訊寫入到本地Relay Log中。

  4. Slave的SQL 執行緒讀取Relay Log中內容在從庫上執行。

圖片

上述過程都是非同步操作,所以在某些涉及到大的變更,例如DDL改變欄位,影響行數較大的寫入、更新或刪除操作都會導致主從間的延遲激增,針對延遲的場景,高版本的MySQL逐步引入了一些新的特性來幫助提高事務在從庫重放的速度。

  • Relay Log的意義

Relay log在本質上可以認為和binlog是等同的紀錄檔檔案,即使是直接在本地開啟兩者也只能發現很少的差異;

Binlog Version 3 (MySQL 4.0.2 - < 5.0.0)

added the relay logs and changed the meaning of the log position

在MySQL 4.0 之前是沒有Relay Log這部分的,整個過程中只有兩個執行緒。但是這樣也帶來一個問題,那就是複製的過程需要同步的進行,很容易被影響,而且效率不高。例如主庫必須要等待從庫讀取完了才能傳送下一個binlog事件。這就有點類似於一個阻塞的通道和非阻塞的通道。

在流程中新增Relay Log中繼紀錄檔後,讓原本同步的獲取事件、重放事件解耦了,兩個步驟可以非同步的進行,Relay Log充當了緩衝區的作用。Relay Log包含一個relay-log.info的檔案,用於記錄當前複製的進度,下一個事件從什麼Pos開始寫入,該檔案由SQL執行緒負責更新。

對於後續逐漸引入的特殊複製模式,會存在一些差異,但整體來說,是按照這個流程來完成的。

2.3.2 半同步複製

非同步複製的場景下,不能確保從庫實時更新到和主庫一致的狀態,那麼如果在出現延遲的背景下發生主庫故障,那麼兩者間的差異資料還是無法進行保障,同時也無法在這種情況下進行讀寫分離,而如果說由非同步改為完全同步,那麼效能開銷上又會大幅提高,很難滿足實際使用的需求。

基於這一的背景,MySQL從5.5版本開始引入了半同步複製機制來降低資料丟失的概率,在這種複製模式中,MySQL讓Master在某一個時間點等待一個Slave節點的 ACK(Acknowledge Character)訊息,接收到ACK訊息後才進行事務提交,這樣既可以減少對效能的影響,還可以相對非同步複製獲得更強的資料可靠性。

介紹半同步複製之前先快速過一下 MySQL 事務寫入碰到主從複製時的完整過程,主庫事務寫入分為 4個步驟:

  1. InnoDB Redo File Write (Prepare Write)

  2. Binlog File Flush & Sync to Binlog File

  3. InnoDB Redo File Commit(Commit Write)

  4. Send Binlog to Slave

 

  • 當Master不需要關注Slave是否接受到Binlog Event時,即為非同步主從複製

  • 當Master需要在第3步Commit Write回覆使用者端前等待Slave的ACK時,為半同步複製(after-commit)

  • 當Master需要在第2步Flush&Sync,即Commit前等待Slave的ACK時,為增強半同步複製(after-sync)

 

  • 時序關係

從半同步複製的時序圖來看,實際上只是在主庫Commit的環節多了等待接收從庫ACK的階段,這裡只需要收到一個從節點的ACK即可繼續正常的處理流程,這種模式下,即使主庫宕機了,也能至少保證有一個從庫節點是可以用的,此外還減少了同步時的等待時間。

圖片

2.3.3 小結

在當前生產環境的線上資料庫版本背景下,由MySQL官方提供的複製方式主要如上文介紹的內容,當然目前有還很多基於MySQL或相容MySQL的衍生資料庫產品,能在可用性和可靠性上做更大的提升,本文就不繼續展開這部分的描述。

2.4 複製的特性

目前已經提及的複製方式,存在一個顯著的特性:無法迴避資料延遲的場景,非同步複製會使得從庫的資料落後,而半同步複製則會阻塞主庫的寫入,影響效能。

MySQL早期的複製模式中,從庫的IO執行緒和SQL執行緒本質上都是序列獲取事件並讀取重放的,只有一個執行緒負責執行Relaylog,但主庫本身接收請求是可以並行地,效能上限只取決於機器資源瓶頸和MySQL處理能力的上限,主庫的執行和從庫的執行(SQL執行緒應用事件)是很難對齊的,這裡參照一組測試資料:

  • 機器:64核 256G,MySQL 5.7.29

  • 測試場景:常規的INSERT,UPDATE壓測場景

  • 結果:MySQL Server的IO執行緒速度以網路上的資料量評估,每秒超過100MB,正常是可以覆蓋業務使用的,然而SQL執行緒的預估速度只有21~23MB/s,如果是涉及UPDATE場景,效能還會減少;

  • 需要注意的是,以上結果是在高版本的MySQL具備並行複製能力的前提下取得,如果是不具備該特性的版本,效能會更差。

期望業務層限制使用是不現實的,MySQL則在5.6版本開始嘗試引入可用的並行複製方案,總的來說,都是通過嘗試加強在從庫層面的應用速度的方式。

2.4.1 基於Schema級別的並行複製

基於庫級別的並行複製是出於一個非常簡易的原則,範例中不同Database/Schema內的資料以及資料變更是無關的,可以並行去處置。

在這種模式中,MySQL的從節點會啟動多個WorkThread ,而原來負責回放的SQLThread會轉變成Coordinator角色,負責判斷事務能否並行執行並分發給WorkThread。

如果事務分別屬於不同的Schema,並且不是DDL語句,同時沒有跨Schema操作,那麼就可以並行回放,否則需要等所有Worker執行緒執行完成後再執行當前紀錄檔中的內容。

MySQL Server
 
MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| aksay_record       |
| mysql              |
| performance_schema |
| proxy_encrypt      |
| sys                |
| test               |
+--------------------+
7 rows in set (0.06 sec)

對於從庫而言,如果接收到了來自主庫的aksay_record以及proxy_encrypt內的資料變更,那麼它是可以同時去處理這兩部分Schema的資料的。

但是這種方式也存在明顯缺陷和不足,首先只有多個Schema流量均衡的情況下才會有較大的效能改善,但如果存在熱點表或範例上只有一個Schema有資料變更,那麼這種並行模式和早期的序列複製也不存在差異;同樣,雖然不同Schema的資料是沒有關聯,這樣並行執行也會影響事務的執行順序,某種程度來說,整個Server的因果一致性被破壞了。

2.4.2 基於組提交的複製(Group Commit)

基於Schema的並行複製在大部分場景是沒有效力的,例如一庫多表的情況下,但改變從庫的單執行執行緒的思路被延續了下來,在5.7版本新增加了一種基於事務組提交的並行複製方式,在具體介紹應用在複製中的組提交策略前,需要先介紹Server本身Innodb引擎提交事務的邏輯:

Binlog的落盤是基於sync_binlog的設定來的,正常情況都是取sync_binlog=1,即每次事務提交就發起fsync刷盤。

主庫在大規模並行執行事務時,因為每個事務都觸發加鎖落盤,反而使得所有的Binlog序列落盤,成為效能上的瓶頸。針對這個問題,MySQL本身在5.6版本引入了事務的組提交能力(這裡並不是指在從庫上應用的邏輯),設計原理很容易理解,只要是能在同一個時間取得資源,開啟Prepare的所有事務,都是可以同時提交的。

在主庫具有這一能力的背景下,可以很容易得發現從庫也可以應用相似的機制來並行地去執行事務,下面介紹MySQL具體實現經歷的兩個階段:

  • 基於Commit-Parents-Based

MySQL中寫入是基於鎖的並行控制,所以所有在Master端同時處於Prepare階段且未提交的事務就不會存在鎖衝突,在Slave端執行時都可以並行執行。

因此可以在所有的事務進入prepare階段的時候標記上一個logical timestamp(實現中使用上一個提交事務的sequence_number),在Slave端同樣timestamp的事務就可以並行執行。

但這種模式會依賴上一個事務組的提交,如果本身是不受資源限制的並行事務,卻會因為它的commit-parent沒有提交而無法執行;

  • 基於Logic-Based

針對Commit-Parent-Based中存在的限制進行了解除,純粹的理解就是隻有當前事務的sequence_number一致就可以並行執行,只根據是否能取得鎖且無衝突的情況即可以並行執行,而不是依賴上一個已提交事務的sequence_number。

三、應用

當前vivo的線上MySQL資料庫服務標準架構是基於一主一從一離線的非同步複製叢集,其中一從用於業務讀請求分離,離線節點不提供讀服務,提供給巨量資料離線和實時抽數/DB平臺查詢以及備份系統使用;針對這樣的應用背景,儲存研發組針對MySQL場景提供了兩種額外的擴充套件服務:

3.1 應用高可用系統+中介軟體

雖然MySQL的主從複製可以提高系統的高可用性,但是MySQL在5.6,5.7版本是不具備類似Redis的自動故障轉移的能力,如果主庫宕機後不進行干預,業務實際上是無法正常寫入的,故障時間較長的情況下,分離在從庫上的讀也會變得不可靠。

3.1.1 VSQL(原高可用2.0架構)

那麼在當前這樣標準一主二從架構的基礎上,為系統增加HA高可用元件以及中介軟體元件強化MySQL服務的高可用性、讀拓展性、資料可靠性:

  • HA元件管理MySQL的複製拓撲,負責監控叢集的健康狀態,管理故障場景下的自動故障轉移;

  • 中介軟體Proxy用於管理流量,應對原有域名場景下變更解析慢或快取不生效的問題,控制讀寫分離、實現IP、SQL的黑白名單等;

圖片

 

3.1.2 資料可靠性強化

資料本身還是依賴MySQL原生的主從複製模式在叢集中同步,這樣仍然存在非同步複製本身的風險,發生主庫宕機時,如果從庫上存在還未接收到的主庫資料,這部分就會丟失,針對這個場景,我們提供了三種可行的方案:

  • 紀錄檔遠端複製

設定HA的中心節點和全網MySQL機器的登入機器後,按照經典的MHA紀錄檔檔案複製補償方案來保障故障時的資料不丟失,操作上即HA節點會存取故障節點的本地檔案目錄讀取候選主節點缺失的Binlog資料並在候選主上重放。

優勢

  • 與1.0的MHA方案保持一致,可以直接使用舊的機制

  • 機制改造後可以混合在高可用的能力內,不需要機器間的免密互信,降低許可權需求和安全風險

劣勢

  • 不一定可用,需要故障節點所在機器可訪達且硬碟正常,無法應對硬體或網路異常的情況

  • 網路上鍊路較長,可能無法控制中間重放紀錄檔的耗時,導致服務較長時間不可用

  • 紀錄檔集中儲存

依賴資料傳輸服務中的BinlogServer模組,提供Binlog紀錄檔的集中儲存能力,HA元件同時管理MySQL叢集以及BinlogServer,強化MySQL架構的健壯性,真實從庫的複製關係全部建立在BinlogServer上,不直接連線主庫。

優勢

  • 可以自定義紀錄檔的儲存形式:檔案系統或其它共用儲存模式

  • 不涉及機器可用和許可權的問題

  • 間接提高binlog的儲存安全性(備份)

劣勢

  • 額外的資源使用,如果需要保留較長時間的紀錄檔,資源使用量較大

  • 如果不開啟半同步,也不能保證所有的binlog紀錄檔都能被採集到,即使採集(相當於IO執行緒)速度遠超relay速度,極限約110MB/s

  • 系統複雜度提升,需要承受引入額外連路的風險

 

  • 改變為半同步複製

MySQL叢集開啟半同步複製,通過設定防止退化(風險較大),Agent本身支援半同步叢集的相關監控,可以減少故障切換時紀錄檔丟失的量(相比非同步複製)

優勢

  • MySQL原生的機制,不需要引入額外的風險

  • 本質上就是在強化高可用的能力(MySQL叢集本身)

  • HA元件可以無縫接入開啟半同步的叢集,不需要任何改造

劣勢

  • 存在不相容的版本,不一定可以開啟

  • 業務可能無法接受效能下降的後果

  • 半同步不能保證完全不丟資料,Agent本身機制實際上是優先選擇「執行最多」的從節點而不是「紀錄檔最多」的從節點

orchestrator will promote the replica which has executed more events rather than the replica which has more data in the relay logs.

目前來說,我們採用的是紀錄檔遠端複製的方案,同時今年在規劃集中儲存的BinlogServer方案來強化資料安全性;不過值得一提的是,半同步也是一種有效可行的方式,對於讀多寫少的業務實際上是可以考慮升級叢集的能力,這樣本質上也可以保證分離讀流量的準確性。

3.2 資料傳輸服務

3.2.1 基於Binlog的跨系統資料流轉

通過利用Binlog,實時地將MySQL的資料流轉到其它系統,包括MySQL,ElasticSearch,Kafka等MQ已經是一種非常經典的應用場景了,MySQL原生提供的這種變化資料同步的能力使其可以有效地在各個系統間實時聯動,DTS(資料傳輸服務)針對MySQL的採集也是基於和前文介紹的複製原理一致的方法,這裡介紹我們是如何利用和MySQL 從節點相同的機制去獲取資料的,也是對於完整開啟複製的拓展介紹:

(1)如何獲取Binlog

比較常規的方式有兩種:

  • 監聽Binlog檔案,類似紀錄檔採集系統的操作

  • MySQL Slave的機制,採集者偽裝成Slave來實現

本文只介紹第二種,Fake Slave的實現方式

 

(2)註冊Slave身份

這裡以GO SDK為例,GO的byte範圍是0~255,其它語言做對應轉換即可。

data := make([]byte, 4+1+4+1+len(hostname)+1+len(b.cfg.User)+1+len(b.cfg.Password)+2+4+4)
  1. 第0-3位為0,無意義

  2. 第4位元是MySQL協定中的Command_Register_Slave,byte值為21

  3. 第5-8位元是當前範例預設的server_id(非uuid,是一個數值)使用小端編碼成的4個位元組

  4. 接下來的若干位是把當前範例的hostname,user,password

  5. 接下來的2位是小端編碼的port埠值

  6. 最後8位元一般都置為0,其中最後4位元指master_id,偽裝slave設定為0即可

(3)發起複製指令

data := make([]byte, 4+1+4+2+4+len(p.Name))

 

  1. 第0-3位同樣置為0,無特殊意義

  2. 第4位元是MySQL協定的Command_Binlog_Dump,byte值為18

  3. 第5-8位元是Binlog Position值的小端序編碼產生的4位元位元組

  4. 第9-10位是MySQL Dump的類別,預設是0,指Binlog_Dump_Never_Stop,即編碼成2個0值

  5. 第11-14位元是範例的server_id(非uuid)基於小端編碼的四個位元組值

  6. 最後若干位即直接追加Binlog File名稱

以上兩個命令通過使用者端連線執行後,就可以在主庫上觀察到一個有效的複製連線。

3.2.2 利用並行複製模式提升效能

以上兩個命令通過使用者端連線執行後,就可以在主庫上觀察到一個有效的複製連線。

根據早期的效能測試結果,不做任何優化,直接單連線重放源叢集資料,在網路上的平均傳輸速度在7.3MB/s左右,即使是和MySQL的SQL Relay速度相比也是相差很遠,在高壓場景下很難滿足需求。

DTS消費單元實現了對消費自kafka的事件的事務重組以及並行的事務解析工作,但實際最終執行還是序列單執行緒地向MySQL回放,這一過程使得效能瓶頸完全集中在了序列執行這一步驟。

  1. MySQL 5.7版本以前,會利用事務的Schema屬性,使不同db下的DML操作可以在備庫並行回放。在優化後,可以做到不同表table下並行。但是如果業務在Master端高並行寫入一個庫(或者優化後的表),那麼slave端就會出現較大的延遲。基於schema的並行複製,Slave作為唯讀範例提供讀取功能時候可以保證同schema下事務的因果序(Causal Consistency,本文討論Consistency的時候均假設Slave端為唯讀),而無法保證不同schema間的。例如當業務關注事務執行先後順序時候,在Master端db1寫入T1,收到T1返回後,才在db2執行T2。但在Slave端可能先讀取到T2的資料,才讀取到T1的資料。

  2. MySQL 5.7的LOGICAL CLOCK並行複製,解除了schema的限制,使得在主庫對一個db或一張表並行執行的事務到slave端也可以並行執行。Logical Clock並行複製的實現,最初是Commit-Parent-Based方式,同一個commit parent的事務可以並行執行。但這種方式會存在可以保證沒有衝突的事務不可以並行,事務一定要等到前一個commit parent group的事務全部回放完才能執行。後面優化為Lock-Based方式,做到只要事務和當前執行事務的Lock Interval都存在重疊,即保證了Master端沒有鎖衝突,就可以在Slave端並行執行。LOGICAL CLOCK可以保證非並行執行事務,即當一個事務T1執行完後另一個事務T2再開始執行場景下的Causal Consistency。

(1)連線池改造

舊版的DTS的每一個消費任務只有一條維持的MySQL長連線,該消費鏈路的所有的事務都在這條長連線上序列執行,產生了極大的效能瓶頸,那麼考慮到並行執行事務的需求,不可能對連線進行並行複用,所以需要改造原本的單連線物件,提升到近似連線池的機制。

go-mysql/client包本身不包含連線池模式,這裡基於事務並行解析的並行度在啟動時,擴充套件存活連線的數量。

// 初始化使用者端連線數
se.conn = make([]*Connection, meta.MaxConcurrenceTransaction)

(2)並行選擇連線

  • 利用邏輯時鐘

開啟GTID複製的模式下,binlog中的GTID_EVENT的正文內會包含兩個值:

LastCommitted  int64
SequenceNumber int64

lastCommitted是我們並行的依據,原則上,LastCommitted相等事務可以並行執行,結合原本事務並行解析完成後會產生並行度(設定值)數量的事務集合,那麼對這個列表進行分析判斷,進行事務到連線池的分配,實現一種近似負載均衡的機制。

  • 非並行項互斥

對於並行執行的場景,可以比較簡單地使用類似負載均衡的機制,從連線池中遍歷mysql connection執行對應的事務;但需要注意到的是,源的事務本身是具有順序的,在logical-clock的場景下,存在部分並行prepare的事務是可以被並行執行的,但仍然有相當一部分的事務是不可並行執行,它們顯然是分散於整個事務佇列中,可以認為並行事務(最少2個)是被不可並行事務包圍的:

假定存在一個事務佇列有6個元素,其中只有t1、t2和t5、t6可以並行執行,那麼執行t3時,需要t1、t2已經執行完畢,執行t5時需要t3,t4都執行完畢。

圖片

(3)校驗點更新

在並行的事務執行場景下,存在水位低的事務後執行完,而水位高的事務先執行完,那麼依照原本的機制,更低的水位會覆蓋掉更高的水位,存在一定的風險:

  1. Write_Event的構造SQL調整為replace into,可以迴避衝突重複的寫事件;Update和Delete可以基於邏輯時鐘的並行保障,不會出現。

  2. 水位只會向上提升,不會向下降低。

但不論怎樣進行優化,並行執行事務必然會引入更多的風險,例如並行事務的回滾無法控制,目標範例和源範例的因果一致性被破壞等,業務可以根據自身的需要進行權衡,是否開啟並行的執行。

基於邏輯時鐘並行執行事務改造後,消費端的執行效能在同等的測試場景下,可以從7.3MB/s提升到13.4MB/s左右。

(4)小結

基於消費任務本身的庫、表過濾,可以實現另一種形式下的並行執行,可以啟動複數的消費任務分別支援不同的庫、表,這也是利用了kafka的多消費者組支援,可以橫向擴充套件以提高並行效能,適用於資料遷移場景,這一部分可以專門提供支援。

而基於邏輯時鐘的方式,對於目前現網大規模存在的未開啟GTID的叢集是無效的,所以這一部分我們也一直在尋找更優的解決方案,例如更高版本的特性Write Set的合併等,繼續做效能優化。

四、總結

最後,關於MySQL的複製能力不僅對於MySQL資料庫服務本身的可用性、可靠性有巨大的提升,也提供了Binlog這一非常靈活的開放式的資料介面用於擴充套件資料的應用範圍,通過利用這個「介面」,很容易就可以達成資料在多個不同儲存結構、環境的實時同步,未來儲存組也將會聚焦於BinlogServer這一擴充套件服務來強化MySQL的架構,包括但不限於資料安全性保障以及對下游資料鏈路的開放等。

參考資料: