本文詳細(xì)描述了PhxSQL的設(shè)計(jì)與實(shí)現(xiàn)。從MySQL的容災(zāi)缺陷開始講起,接著闡述實(shí)現(xiàn)高可用強(qiáng)一致的思路,然后具體分析每個(gè)實(shí)現(xiàn)環(huán)節(jié)要注意的要點(diǎn)和解決方案,后展示了PhxSQL在容災(zāi)和性能上的成果。
設(shè)計(jì)背景
互聯(lián)網(wǎng)應(yīng)用中賬號(hào)和金融類關(guān)鍵系統(tǒng)要求和強(qiáng)調(diào)強(qiáng)一致性及高可用性。當(dāng)面臨機(jī)器損壞、網(wǎng)絡(luò)分區(qū)、主備手工或者自動(dòng)切換時(shí),傳統(tǒng)的MySQL主備難以保證強(qiáng)一致性和高可用性。PhxSQL將MySQL集群構(gòu)建在一致性完善的Paxos協(xié)議基礎(chǔ)上,保證了集群內(nèi)MySQL機(jī)器之間數(shù)據(jù)的強(qiáng)一致性和整個(gè)集群的高可用性。
原生MySQL的容災(zāi)缺陷
【MySQL容災(zāi)方案】
MySQL有兩種常見的復(fù)制方案,異步復(fù)制和半同步復(fù)制。

【MySQL重啟流程】
半同步方案中的“半”是指Master在等待Slave的ACK失敗時(shí)將退化成異步復(fù)制。同時(shí),MySQL在重啟時(shí)也不會(huì)執(zhí)行半同步復(fù)制。
如圖3中的id(Gtid)=101數(shù)據(jù)是Master機(jī)器中新寫入到Binlog File的Binlog數(shù)據(jù)。但Master在復(fù)制數(shù)據(jù)到Slave的過程中MySQL宕機(jī)導(dǎo)致復(fù)制失敗。MySQL重啟時(shí),數(shù)據(jù)(id=101)會(huì)被直接進(jìn)行commit操作,隨后再將數(shù)據(jù)異步復(fù)制到Slave。(下文將已經(jīng)寫入到Binlog File但未進(jìn)行commit操作的數(shù)據(jù)(id=101)稱為Pending Binlog。)
該情況下MySQL容易出現(xiàn)Master-Slave之間數(shù)據(jù)不一致的情況,官方也描述了該問題。
【MySQL重啟缺陷】
下面將解釋MySQL在重啟時(shí)不執(zhí)行半同步會(huì)產(chǎn)生數(shù)據(jù)不一致的原因。
當(dāng)對上述例子中的Pending Binlog(id=101)進(jìn)行復(fù)制時(shí)Master宕機(jī)導(dǎo)致復(fù)制失敗,隨后Slave1切換成新Master并開始提供服務(wù)(寫入id=201的數(shù)據(jù))。此后,當(dāng)舊Master重啟時(shí),Pending Binlog(id=101)不會(huì)被重新進(jìn)行復(fù)制而直接進(jìn)行commit操作,從而導(dǎo)致舊Master比新Master多了一條數(shù)據(jù),舊Master無法成為新Master的Slave,需要人工處理掉這條數(shù)據(jù)之后,才能讓舊Master作為Slave提供服務(wù),如圖4所示。
上述case只對舊Master的數(shù)據(jù)造成影響,不會(huì)使得MySQL Client讀取到錯(cuò)誤數(shù)據(jù)。但當(dāng)Master連續(xù)出現(xiàn)兩次宕機(jī)后產(chǎn)生Master切換,兩次宕機(jī)間隔較短使得Pending Binlog未能及時(shí)復(fù)制到Slave,且期間有查詢請求時(shí)(Master宕機(jī)→Master重啟→查詢數(shù)據(jù)→Master宕機(jī)→Master切換),MySQL Client會(huì)產(chǎn)生如圖5所示的幻讀(兩次讀到的結(jié)果不一致)。
【MySQL Client分裂】
當(dāng)Master出現(xiàn)故障且產(chǎn)生Master切換時(shí),由于原生MySQL缺乏調(diào)用端的通知/重定向機(jī)制,使得不同的Client可能訪問不同的Master,導(dǎo)致數(shù)據(jù)的錯(cuò)誤寫入和讀取,如圖6所示。
【MySQL缺乏自動(dòng)選主機(jī)制】
由于半同步復(fù)制不需要等待所有Slave的ACK,因此當(dāng)Master出現(xiàn)故障時(shí),需要選有新Binlog的Slave為新的Master;而MySQL并沒有內(nèi)置這個(gè)選主機(jī)制,如圖7所示。
【MySQL的容災(zāi)缺陷總結(jié)】
MySQL在容災(zāi)方面存在的問題:
對于原生MySQL,在高可用和強(qiáng)一致兩個(gè)特性中,只能二選一:
因此MySQL在容災(zāi)上無法同時(shí)滿足數(shù)據(jù)強(qiáng)一致和服務(wù)高可用兩個(gè)特性。
PhxSQL設(shè)計(jì)思路
【可靠日志存儲(chǔ)】
實(shí)現(xiàn)一個(gè)以可靠日志存儲(chǔ)為中心的架構(gòu)來解決MySQL數(shù)據(jù)復(fù)制時(shí)產(chǎn)生的數(shù)據(jù)不一致問題。
Master將Binlog發(fā)送到BinlogSvr集群(可靠日志存儲(chǔ)),Slave從BinlogSvr集群獲取Binlog數(shù)據(jù)完成數(shù)據(jù)復(fù)制。
Master在重啟時(shí),根據(jù)BinlogSvr集群的數(shù)據(jù)判斷Pending Binlog是否已經(jīng)被復(fù)制。如果未被復(fù)制則從Binlog File中刪除。
利用BinlogSvr集群(可靠日志存儲(chǔ)),使得Master(重啟時(shí)檢查本地Binlog是否和BinlogSvr集群的數(shù)據(jù)一致)和Slave(從BinlogSvr集群中獲取Binlog)的數(shù)據(jù)保持一致,從而保證了整個(gè)集群中的MySQL主備間數(shù)據(jù)的一致性,如圖8所示。
【請求透傳】
在Master進(jìn)行切換時(shí),切換操作可能會(huì)導(dǎo)致部分MySQL Client仍然訪問舊Master并讀到舊數(shù)據(jù)。
直觀的方法是修改MySQL Client API,在每一次進(jìn)行查詢時(shí),先確認(rèn)當(dāng)前Master的位置。但此方法有以下缺點(diǎn):
為了避免修改MySQL Client API,可通過增加Proxy進(jìn)行請求透傳來解決上述問題。在每一個(gè)MySQL結(jié)點(diǎn)上增加一個(gè)Proxy,MySQL Client的請求不再直接訪問MySQL而直接訪問Proxy。Proxy根據(jù)Master的位置,將訪問Slave機(jī)器的請求透傳到Master機(jī)器,再進(jìn)行MySQL操作。
通過增加Proxy進(jìn)行請求透傳,解決了MySQL Client分裂導(dǎo)致有可能讀取到舊數(shù)據(jù)的問題,如圖9所示。
【自動(dòng)選主】
多機(jī)自動(dòng)選主常見的實(shí)現(xiàn)方式是由各個(gè)參與者發(fā)起投票,獲得多數(shù)派支持的機(jī)器為Master,同時(shí)把Master信息記錄到可靠存儲(chǔ)。Master機(jī)器定期到可靠存儲(chǔ)延長租約;非Master機(jī)器定期檢查Master租約是否過期,從而決定是否要發(fā)起選舉自己為Master的投票。
為了避免修改MySQL代碼,在MySQL機(jī)器上增加一個(gè)Agent,由Agent來替代MySQL發(fā)起選主投票和續(xù)期租約;可靠存儲(chǔ)繼續(xù)由BinlogSvr承擔(dān)。
Agent完成以下功能:

PhxSQL架構(gòu)和實(shí)現(xiàn)
從上述思路可以得出PhxSQL的簡單三層架構(gòu)。對于每一個(gè)節(jié)點(diǎn),部署3個(gè)模塊(PhxSQLProxy,MySQL,PhxBinlogSvr)。多個(gè)節(jié)點(diǎn)上的PhxBinlogSvr組成一個(gè)可靠的日志存儲(chǔ)集群和可靠的Master信息存儲(chǔ)集群;PhxBinlogSvr同時(shí)承擔(dān)Agent的責(zé)任。PhxSQLProxy負(fù)責(zé)請求的透傳。Master結(jié)點(diǎn)上的PhxSync負(fù)責(zé)將MySQL的Binlog發(fā)送到PhxBinlogSvr,如圖11所示。
【Proxy(PhxSQLProxy)】
請求透傳是Proxy主要的功能。主要解決在進(jìn)行Master切換的時(shí)候,MySQL Client會(huì)被分裂,不同的Client可能連接到不同的MySQL。導(dǎo)致出現(xiàn)MySQL Client寫入數(shù)據(jù)到錯(cuò)誤的Master或者從錯(cuò)誤的Master讀取到錯(cuò)誤的數(shù)據(jù)。
Proxy的請求透傳分兩種:

高性能:由于Proxy接管了MySQL Client的請求,為了使整個(gè)集群的讀寫性能接近單機(jī)MySQL,Proxy使用協(xié)程模型提高自身的處理能力。
Proxy的協(xié)程模型使用開源的Libco庫。Libco庫是微信團(tuán)隊(duì)開源的一個(gè)高性能協(xié)程庫,具有以下特點(diǎn):
完全兼容MySQL:為了已有的應(yīng)用程序能夠不做任何修改就能遷移到PhxSQL,Proxy需兼容MySQL的所有功能。
兼容MySQL事務(wù)
MySQL事務(wù)管理基于連接,同一個(gè)事務(wù)的所有請求通過同一個(gè)連接通信。在事務(wù)處理中連接丟失,事務(wù)將被rollback(http://dev.mysql.com/doc/refman/5.6/en/innodb-autocommit-commit-rollback.html)。
Proxy使用1:1連接模型完全兼容MySQL事務(wù)。每當(dāng)MySQL Client發(fā)起一個(gè)連接到Proxy,Proxy都會(huì)相應(yīng)地發(fā)起一個(gè)連接到MySQL。兩條連接中,任意一個(gè)中斷,另外一個(gè)也相應(yīng)斷開,對應(yīng)的事務(wù)會(huì)被rollback,如圖13所示。
兼容MySQL權(quán)限
MySQL的權(quán)限管理基于(用戶,源IP)對,源IP是通過socket句柄反查獲取。當(dāng)請求通過Proxy連接到MySQL時(shí),源IP為Proxy本地IP,權(quán)限管理會(huì)出現(xiàn)異常。
Proxy利用MySQL協(xié)議HEAD保留字段透傳真實(shí)源IP到MySQ,MySQL再從HEAD保留字段獲取正確的源IP進(jìn)行權(quán)限管理,如圖14所示。
PhxSync
PhxSync的功能和MySQL的semisync插件類似。經(jīng)過調(diào)研,對semisync插件的接口做少量的調(diào)整,就可以使用這些插件接口來實(shí)現(xiàn)PhxSync。
PhxSync功能主要是:
由于MySQL沒有提供在重啟時(shí)的插件接口,為了后續(xù)維護(hù)方便,在MySQL代碼層抽象出了一個(gè)新插件接口before_binlog_init用于校準(zhǔn)Binlog。
上述對after_flush接口的調(diào)整,和新增的before_binlog_init接口已經(jīng)提交補(bǔ)丁給MySQL官方(http://bugs.mysql.com/bug.php?id=83158)。
【PhxBinlogSvr】
PhxBinlogSvr主要負(fù)責(zé)存儲(chǔ)Binlog和Master信息的維護(hù)。在數(shù)據(jù)復(fù)制階段,通過Paxos協(xié)議保證PhxBinlogSvr各節(jié)點(diǎn)的數(shù)據(jù)一致性(下文稱PhxBinlogSvr為BinlogSvr)。
BinlogSvr異常情況處理
防止Slave的節(jié)點(diǎn)提交數(shù)據(jù)
當(dāng)舊Master在提交數(shù)據(jù)時(shí)由于網(wǎng)絡(luò)問題數(shù)據(jù)包被卡在網(wǎng)絡(luò),且新Mater已經(jīng)成功切換時(shí),或者人為錯(cuò)誤直接往Slave節(jié)點(diǎn)的MySQL寫入數(shù)據(jù)時(shí),則會(huì)出現(xiàn)Slave節(jié)點(diǎn)提交數(shù)據(jù)的情況。多節(jié)點(diǎn)同時(shí)提交數(shù)據(jù)會(huì)出現(xiàn)BinlogSvr的Binlog數(shù)據(jù)和MySQL存儲(chǔ)的Binlog數(shù)據(jù)不一致的情況。
BinlogSvr存儲(chǔ)了集群內(nèi)的Master信息。當(dāng)其收到MySQL提交的數(shù)據(jù)時(shí),可根據(jù)Master信息拒絕非Master節(jié)點(diǎn)的提交,如圖15所示。
防止Master提交錯(cuò)誤數(shù)據(jù)
在某些情況下,Master可能會(huì)重新發(fā)送數(shù)據(jù)或者發(fā)送錯(cuò)誤數(shù)據(jù)。譬如在網(wǎng)絡(luò)不好的情況下Master由于提交數(shù)據(jù)超時(shí)而重發(fā)數(shù)據(jù)。磁盤發(fā)生故障或者數(shù)據(jù)被錯(cuò)誤回滾或者修改的時(shí)候,Master會(huì)提交錯(cuò)誤的數(shù)據(jù)。
BinlogSvr使用樂觀鎖機(jī)制來防止Master的異常提交。在MySQL提交數(shù)據(jù)給BinlogSvr時(shí),以本機(jī)MySQL已經(jīng)執(zhí)行的GTID為樂觀鎖,提交的內(nèi)容為(本機(jī)MySQL已經(jīng)執(zhí)行的新GTID,本次要提交的Binlog)。BinlogSvr通過檢查請求中(本機(jī)MySQL已經(jīng)執(zhí)行的新GTID)和自身保存的新GTID是否匹配來拒絕重新發(fā)送或者異常發(fā)送的數(shù)據(jù),如圖16所示。
支持MySQL原生復(fù)制協(xié)議:為了讓Slave能從BinlogSvr獲取Binlog,好的方式就是BinlogSvr支持MySQL原生的復(fù)制協(xié)議,這樣不用對Slave做任何修改,如圖17所示。
Master管理:BinlogSvr除了存儲(chǔ)MySQL的Binlog數(shù)據(jù),還存儲(chǔ)了Master信息。同時(shí)還承擔(dān)了Agent的角色,負(fù)責(zé)監(jiān)控MySQL的狀態(tài),必要時(shí)發(fā)起選舉自己為Master的投票。
BinlogSvr通過Paxos協(xié)議進(jìn)行Master選舉,選舉成功后成為Master并擁有租約。通過Paxos協(xié)議選舉保證了終只產(chǎn)生一個(gè)Master且每個(gè)節(jié)點(diǎn)記錄了一致的Master信息。
PhxSQL效果
【PhxSQL數(shù)據(jù)一致性】
通過比較PhxSQL集群中各節(jié)點(diǎn)的數(shù)據(jù)(MySQL Binlog,PhxPaxos,BinlogSvr) 判斷各節(jié)點(diǎn)數(shù)據(jù)是否一致,如圖18所示。
【Master自動(dòng)切換】
通過觀察Master宕機(jī)時(shí)各節(jié)點(diǎn)的流量變化判斷Master是否順利切換。下圖中的紅線代表流量。當(dāng)Master宕機(jī)時(shí),流量會(huì)隨之轉(zhuǎn)移,代表Master順利切換,如圖19所示。
【PhxSQL性能】
機(jī)器信息:
工具和參數(shù):
PhxSQL的寫性能比MySQL的半同步好,讀性能由于多了一層Proxy導(dǎo)致比MySQL的半同步稍差。

本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對此聲明的最終解釋權(quán)。