本文詳細描述了PhxSQL的設計與實現(xiàn)。從MySQL的容災缺陷開始講起,接著闡述實現(xiàn)高可用強一致的思路,然后具體分析每個實現(xiàn)環(huán)節(jié)要注意的要點和解決方案,最后展示了PhxSQL在容災和性能上的成果。
設計背景
互聯(lián)網應用中賬號和金融類關鍵系統(tǒng)要求和強調強一致性及高可用性。當面臨機器損壞、網絡分區(qū)、主備手工或者自動切換時,傳統(tǒng)的MySQL主備難以保證強一致性和高可用性。PhxSQL將MySQL集群構建在一致性完善的Paxos協(xié)議基礎上,保證了集群內MySQL機器之間數(shù)據(jù)的強一致性和整個集群的高可用性。
原生MySQL的容災缺陷 【MySQL容災方案】
MySQL有兩種常見的復制方案,異步復制和半同步復制。
1、異步復制方案
Master對數(shù)據(jù)進行commit操作后再將數(shù)據(jù)異步復制到Slave。
但數(shù)據(jù)無法保證成功復制,也就無法保證MySQL主備間的數(shù)據(jù)一致性,如圖1所示。
圖1 MySQL異步復制流程
2、半同步復制方案
Master對數(shù)據(jù)進行commit操作前將數(shù)據(jù)復制到Slave,確認復制成功后再對數(shù)據(jù)進行commit操作。
絕大多數(shù)情況下,半同步復制能保證MySQL主備間的數(shù)據(jù)一致性,如圖2所示。
圖2 MySQL半同步復制流程
【MySQL重啟流程】
半同步方案中的“半”是指Master在等待Slave的ACK失敗時將退化成異步復制。同時,MySQL在重啟時也不會執(zhí)行半同步復制。
如圖3中的id(Gtid)=101數(shù)據(jù)是Master機器中新寫入到Binlog File的Binlog數(shù)據(jù)。但Master在復制數(shù)據(jù)到Slave的過程中MySQL宕機導致復制失敗。MySQL重啟時,數(shù)據(jù)(id=101)會被直接進行commit操作,隨后再將數(shù)據(jù)異步復制到Slave。(下文將已經寫入到Binlog File但未進行commit操作的數(shù)據(jù)(id=101)稱為Pending Binlog。)
圖3 MySQL重啟時直接提交Pending Binlog
該情況下MySQL容易出現(xiàn)Master-Slave之間數(shù)據(jù)不一致的情況,官方也描述了該問題。
http://bugs.mysql.com/bug.php?id=80395
https://mariadb.atlassian.net/browse/MDEV-162
【MySQL重啟缺陷】
下面將解釋MySQL在重啟時不執(zhí)行半同步會產生數(shù)據(jù)不一致的原因。
當對上述例子中的Pending Binlog(id=101)進行復制時Master宕機導致復制失敗,隨后Slave1切換成新Master并開始提供服務(寫入id=201的數(shù)據(jù))。此后,當舊Master重啟時,Pending Binlog(id=101)不會被重新進行復制而直接進行commit操作,從而導致舊Master比新Master多了一條數(shù)據(jù),舊Master無法成為新Master的Slave,需要人工處理掉這條數(shù)據(jù)之后,才能讓舊Master作為Slave提供服務,如圖4所示。
圖4 MySQL重啟缺陷導致主備數(shù)據(jù)不一致
上述case只對舊Master的數(shù)據(jù)造成影響,不會使得MySQL Client讀取到錯誤數(shù)據(jù)。但當Master連續(xù)出現(xiàn)兩次宕機后產生Master切換,兩次宕機間隔較短使得Pending Binlog未能及時復制到Slave,且期間有查詢請求時(Master宕機→Master重啟→查詢數(shù)據(jù)→Master宕機→Master切換),MySQL Client會產生如圖5所示的幻讀(兩次讀到的結果不一致)。
圖5 MySQL重啟缺陷導致Client產生幻讀
【MySQL Client分裂】
當Master出現(xiàn)故障且產生Master切換時,由于原生MySQL缺乏調用端的通知/重定向機制,使得不同的Client可能訪問不同的Master,導致數(shù)據(jù)的錯誤寫入和讀取,如圖6所示。
圖6 MySQL進行Master導致Client端分裂
【MySQL缺乏自動選主機制】
由于半同步復制不需要等待所有Slave的ACK,因此當Master出現(xiàn)故障時,需要選有最新Binlog的Slave為新的Master;而MySQL并沒有內置這個選主機制,如圖7所示。
圖7 MySQL缺少自動選主機制
【MySQL的容災缺陷總結】
MySQL在容災方面存在的問題:
Master切換時主備數(shù)據(jù)不能保證一致:Master重啟并切換可能導致MySQL主備間數(shù)據(jù)不一致。Master重啟并切換可能導致MySQL Client產生幻讀。
原生MySQL缺乏高可用機制:Master切換導致調用端分裂。缺乏自動選主機制。
對于原生MySQL,在高可用和強一致兩個特性中,只能二選一:
要求MySQL主備間的數(shù)據(jù)強一致,不做主備自動切換。
借助MHA實現(xiàn)高可用,容忍MySQL主備間的數(shù)據(jù)不一致。
因此MySQL在容災上無法同時滿足數(shù)據(jù)強一致和服務高可用兩個特性。
PhxSQL設計思路
【可靠日志存儲】
實現(xiàn)一個以可靠日志存儲為中心的架構來解決MySQL數(shù)據(jù)復制時產生的數(shù)據(jù)不一致問題。
Master將Binlog發(fā)送到BinlogSvr集群(可靠日志存儲),Slave從BinlogSvr集群獲取Binlog數(shù)據(jù)完成數(shù)據(jù)復制。
Master在重啟時,根據(jù)BinlogSvr集群的數(shù)據(jù)判斷Pending Binlog是否已經被復制。如果未被復制則從Binlog File中刪除。
利用BinlogSvr集群(可靠日志存儲),使得Master(重啟時檢查本地Binlog是否和BinlogSvr集群的數(shù)據(jù)一致)和Slave(從BinlogSvr集群中獲取Binlog)的數(shù)據(jù)保持一致,從而保證了整個集群中的MySQL主備間數(shù)據(jù)的一致性,如圖8所示。
圖8 實現(xiàn)一個可靠日志存儲保證各MySQL的數(shù)據(jù)一致
【請求透傳】
在Master進行切換時,切換操作可能會導致部分MySQL Client仍然訪問舊Master并讀到舊數(shù)據(jù)。
最直觀的方法是修改MySQL Client API,在每一次進行查詢時,先確認當前Master的位置。但此方法有以下缺點:
需要維護一個MySQL Client API的私有版本,維護成本高。
所有的調用端需要集成這個私有的MySQL Client API,操作成本很高。
為了避免修改MySQL Client API,可通過增加Proxy進行請求透傳來解決上述問題。在每一個MySQL結點上增加一個Proxy,MySQL Client的請求不再直接訪問MySQL而直接訪問Proxy。Proxy根據(jù)Master的位置,將訪問Slave機器的請求透傳到Master機器,再進行MySQL操作。
通過增加Proxy進行請求透傳,解決了MySQL Client分裂導致有可能讀取到舊數(shù)據(jù)的問題,如圖9所示。
圖9 實現(xiàn)一個可靠日志存儲保證各MySQL的數(shù)據(jù)一致
【自動選主】
多機自動選主最常見的實現(xiàn)方式是由各個參與者發(fā)起投票,獲得多數(shù)派支持的機器為Master,同時把Master信息記錄到可靠存儲。Master機器定期到可靠存儲延長租約;非Master機器定期檢查Master租約是否過期,從而決定是否要發(fā)起選舉自己為Master的投票。
為了避免修改MySQL代碼,在MySQL機器上增加一個Agent,由Agent來替代MySQL發(fā)起選主投票和續(xù)期租約;可靠存儲繼續(xù)由BinlogSvr承擔。
Agent完成以下功能:
Master機器的Agent監(jiān)控本機MySQL是否正常服務;如果正常服務,則定期到可靠存儲延長租約,否則停止續(xù)約。
非Master機器的Agent定期從可靠存儲檢查Master租約是否過期;如果過期,再檢查本機MySQL是否已經執(zhí)行了所有Binlog。如果已經執(zhí)行了所有Binlog,則發(fā)起選舉自己為Master的投票,如圖10所示。
圖10 可靠日志存儲和Agent共同實現(xiàn)自動選主機制
PhxSQL架構和實現(xiàn)
從上述思路可以得出PhxSQL的簡單三層架構。對于每一個節(jié)點,部署3個模塊(PhxSQLProxy,MySQL,PhxBinlogSvr)。多個節(jié)點上的PhxBinlogSvr組成一個可靠的日志存儲集群和可靠的Master信息存儲集群;PhxBinlogSvr同時承擔Agent的責任。PhxSQLProxy負責請求的透傳。Master結點上的PhxSync負責將MySQL的Binlog發(fā)送到PhxBinlogSvr,如圖11所示。
圖11 PhxSQL基本架構
【Proxy(PhxSQLProxy)】
請求透傳
請求透傳是Proxy主要的功能。主要解決在進行Master切換的時候,MySQL Client會被分裂,不同的Client可能連接到不同的MySQL。導致出現(xiàn)MySQL Client寫入數(shù)據(jù)到錯誤的Master或者從錯誤的Master讀取到錯誤的數(shù)據(jù)。
Proxy的請求透傳分兩種:
讀寫端口請求透傳:Slave節(jié)點收到的請求透傳給Master節(jié)點執(zhí)行。Master節(jié)點收到的請求直接透傳給本機MySQL執(zhí)行。
只讀端口請求透傳:Master節(jié)點收到的請求透傳給Slave節(jié)點執(zhí)行。Slave節(jié)點收到的請求直接透傳給本機MySQL執(zhí)行,如圖12所示。
圖12 Proxy請求透傳流程
高性能:由于Proxy接管了MySQL Client的請求,為了使整個集群的讀寫性能接近單機MySQL,Proxy使用協(xié)程模型提高自身的處理能力。
Proxy的協(xié)程模型使用開源的Libco庫。Libco庫是微信團隊開源的一個高性能協(xié)程庫,具有以下特點:
用同步方式寫代碼,實現(xiàn)異步代碼的性能。
支持千萬級的并發(fā)連接。
項目地址 https://github.com/tencent-wechat/libco
完全兼容MySQL:為了已有的應用程序能夠不做任何修改就能遷移到PhxSQL,Proxy需兼容MySQL的所有功能。
兼容MySQL事務
MySQL事務管理基于連接,同一個事務的所有請求通過同一個連接通信。在事務處理中連接丟失,事務將被rollback( http://dev.mysql.com/doc/refman/5.6/en/innodb-autocommit-commit-rollback.html )。
Proxy使用1:1連接模型完全兼容MySQL事務。每當MySQL Client發(fā)起一個連接到Proxy,Proxy都會相應地發(fā)起一個連接到MySQL。兩條連接中,任意一個中斷,另外一個也相應斷開,對應的事務會被rollback,如圖13所示。
圖13 Proxy的1對1事務連接模型
兼容MySQL權限
MySQL的權限管理基于(用戶,源IP)對,源IP是通過socket句柄反查獲取。當請求通過Proxy連接到MySQL時,源IP為Proxy本地IP,權限管理會出現(xiàn)異常。
Proxy利用MySQL協(xié)議HEAD保留字段透傳真實源IP到MySQ,MySQL再從HEAD保留字段獲取正確的源IP進行權限管理,如圖14所示。
圖14 Proxy通過修改MySQL協(xié)議兼容MySQL權限
PhxSync
PhxSync的功能和MySQL的semisync插件類似。經過調研,對semisync插件的接口做少量的調整,就可以使用這些插件接口來實現(xiàn)PhxSync。
PhxSync功能主要是:
正常運行時提交Binlog:MySQL在正常寫入或者更新數(shù)據(jù)時,會調用after_flush接口。PhxSync插件通過實現(xiàn)after_flush接口將MySQL新寫入的Binlog提交到本機的BinlogSvr,由本機BinlogSvr通過Paxos協(xié)議同步到BinlogSvr集群。
重啟時校準本地Binlog:MySQL在重啟時通過查詢BinlogSvr集群判斷本地Pending Binlog的狀態(tài)。如果Pending Binlog未復制到BinlogSvr集群則從本地刪除,保持本地的Binlog數(shù)據(jù)和BinlogSvr集群的Binlog數(shù)據(jù)一致。
由于MySQL沒有提供在重啟時的插件接口,為了后續(xù)維護方便,在MySQL代碼層抽象出了一個新插件接口before_binlog_init用于校準Binlog。
上述對after_flush接口的調整,和新增的before_binlog_init接口已經提交補丁給MySQL官方( http://bugs.mysql.com/bug.php?id=83158 )。
【PhxBinlogSvr】
PhxBinlogSvr主要負責存儲Binlog和Master信息的維護。在數(shù)據(jù)復制階段,通過Paxos協(xié)議保證PhxBinlogSvr各節(jié)點的數(shù)據(jù)一致性(下文稱PhxBinlogSvr為BinlogSvr)。
PhxPaxos庫:BinlogSvr使用PhxPaxos庫進行數(shù)據(jù)的復制。PhxPaxos庫是微信團隊開源的Paxos類庫,具有以下特性:1. 保證各節(jié)點的數(shù)據(jù)一致。
保證集群機器超過一半存活還能服務。
高性能。
功能完善。
穩(wěn)定性經過大規(guī)模驗證。
接口方便易用。
項目地址 https://github.com/tencent-wechat/phxpaxos
BinlogSvr異常情況處理 防止Slave的節(jié)點提交數(shù)據(jù)
當舊Master在提交數(shù)據(jù)時由于網絡問題數(shù)據(jù)包被卡在網絡,且新Mater已經成功切換時,或者人為錯誤直接往Slave節(jié)點的MySQL寫入數(shù)據(jù)時,則會出現(xiàn)Slave節(jié)點提交數(shù)據(jù)的情況。多節(jié)點同時提交數(shù)據(jù)會出現(xiàn)BinlogSvr的Binlog數(shù)據(jù)和MySQL存儲的Binlog數(shù)據(jù)不一致的情況。
BinlogSvr存儲了集群內的Master信息。當其收到MySQL提交的數(shù)據(jù)時,可根據(jù)Master信息拒絕非Master節(jié)點的提交,如圖15所示。
圖15 BinlogSvr通過Master信息拒絕非Master節(jié)點的提交
防止Master提交錯誤數(shù)據(jù)
在某些情況下,Master可能會重新發(fā)送數(shù)據(jù)或者發(fā)送錯誤數(shù)據(jù)。譬如在網絡不好的情況下Master由于提交數(shù)據(jù)超時而重發(fā)數(shù)據(jù)。磁盤發(fā)生故障或者數(shù)據(jù)被錯誤回滾或者修改的時候,Master會提交錯誤的數(shù)據(jù)。
BinlogSvr使用樂觀鎖機制來防止Master的異常提交。在MySQL提交數(shù)據(jù)給BinlogSvr時,以本機MySQL已經執(zhí)行的GTID為樂觀鎖,提交的內容為(本機MySQL已經執(zhí)行的最新GTID,本次要提交的Binlog)。BinlogSvr通過檢查請求中(本機MySQL已經執(zhí)行的最新GTID)和自身保存的最新GTID是否匹配來拒絕重新發(fā)送或者異常發(fā)送的數(shù)據(jù),如圖16所示。
圖16 BinlogSvr使用樂觀鎖拒絕Master在數(shù)據(jù)異常的情況下提交數(shù)據(jù)
支持MySQL原生復制協(xié)議:為了讓Slave能從BinlogSvr獲取Binlog,最好的方式就是BinlogSvr支持MySQL原生的復制協(xié)議,這樣不用對Slave做任何修改,如圖17所示。
圖17 BinlogSvr支持MySQL使用原生復制協(xié)議獲取Binlog數(shù)據(jù)
Master管理:BinlogSvr除了存儲MySQL的Binlog數(shù)據(jù),還存儲了Master信息。同時還承擔了Agent的角色,負責監(jiān)控MySQL的狀態(tài),必要時發(fā)起選舉自己為Master的投票。
BinlogSvr通過Paxos協(xié)議進行Master選舉,選舉成功后成為Master并擁有租約。通過Paxos協(xié)議選舉保證了最終只產生一個Master且每個節(jié)點記錄了一致的Master信息。
PhxSQL效果 【PhxSQL數(shù)據(jù)一致性】
通過比較PhxSQL集群中各節(jié)點的數(shù)據(jù)(MySQL Binlog,PhxPaxos,BinlogSvr) 判斷各節(jié)點數(shù)據(jù)是否一致,如圖18所示。
圖18 PhxSQL 3機數(shù)據(jù)對比
【Master自動切換】
通過觀察Master宕機時各節(jié)點的流量變化判斷Master是否順利切換。下圖中的紅線代表流量。當Master宕機時,流量會隨之轉移,代表Master順利切換,如圖19所示。
圖19 PhxSQL進行Master切換時各節(jié)點的寫入流量變化
【PhxSQL性能】
MySQL版本:Percona 5.6.31-77.0
機器信息:
CPU :Intel Xeon CPU E5-2420 0 @ 1.90GHz * 24。
Memory : 32G。
Disk:SSD Raid10。
Ping Costs:Master→Slave:3 ~ 4ms; client→Master :4ms。
工具和參數(shù):
1.sysbench。
2.–oltp-tables-count=10 –oltp-table-size=1000000 –num-threads=500。
3.–max-requests=100000 –report-interval=1 –max-time=200。
PhxSQL的寫性能比MySQL的半同步好,讀性能由于多了一層Proxy導致比MySQL的半同步稍差。
圖20 PhxSQL和MySQL的性能對比