微信高可用分布式數(shù)據(jù)庫PhxSQL設(shè)計(jì)與實(shí)現(xiàn)
本文詳細(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ù)制。
1、異步復(fù)制方案
Master對(duì)數(shù)據(jù)進(jìn)行commit操作后再將數(shù)據(jù)異步復(fù)制到Slave。
但數(shù)據(jù)無法保證成功復(fù)制,也就無法保證MySQL主備間的數(shù)據(jù)一致性,如圖1所示。
圖1 MySQL異步復(fù)制流程
2、半同步復(fù)制方案
Master對(duì)數(shù)據(jù)進(jìn)行commit操作前將數(shù)據(jù)復(fù)制到Slave,確認(rèn)復(fù)制成功后再對(duì)數(shù)據(jù)進(jìn)行commit操作。
絕大多數(shù)情況下,半同步復(fù)制能保證MySQL主備間的數(shù)據(jù)一致性,如圖2所示。
圖2 MySQL半同步復(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。)
圖3 MySQL重啟時(shí)直接提交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在重啟時(shí)不執(zhí)行半同步會(huì)產(chǎn)生數(shù)據(jù)不一致的原因。
當(dāng)對(duì)上述例子中的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所示。
圖4 MySQL重啟缺陷導(dǎo)致主備數(shù)據(jù)不一致
上述case只對(duì)舊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,且期間有查詢請(qǐng)求時(shí)(Master宕機(jī)→Master重啟→查詢數(shù)據(jù)→Master宕機(jī)→Master切換),MySQL Client會(huì)產(chǎn)生如圖5所示的幻讀(兩次讀到的結(jié)果不一致)。
圖5 MySQL重啟缺陷導(dǎo)致Client產(chǎn)生幻讀
【MySQL Client分裂】
當(dāng)Master出現(xiàn)故障且產(chǎn)生Master切換時(shí),由于原生MySQL缺乏調(diào)用端的通知/重定向機(jī)制,使得不同的Client可能訪問不同的Master,導(dǎo)致數(shù)據(jù)的錯(cuò)誤寫入和讀取,如圖6所示。
圖6 MySQL進(jìn)行Master導(dǎo)致Client端分裂
【MySQL缺乏自動(dòng)選主機(jī)制】
由于半同步復(fù)制不需要等待所有Slave的ACK,因此當(dāng)Master出現(xiàn)故障時(shí),需要選有***Binlog的Slave為新的Master;而MySQL并沒有內(nèi)置這個(gè)選主機(jī)制,如圖7所示。
圖7 MySQL缺少自動(dòng)選主機(jī)制
【MySQL的容災(zāi)缺陷總結(jié)】
- MySQL在容災(zāi)方面存在的問題:
- Master切換時(shí)主備數(shù)據(jù)不能保證一致:Master重啟并切換可能導(dǎo)致MySQL主備間數(shù)據(jù)不一致。Master重啟并切換可能導(dǎo)致MySQL Client產(chǎn)生幻讀。
- 原生MySQL缺乏高可用機(jī)制:Master切換導(dǎo)致調(diào)用端分裂。缺乏自動(dòng)選主機(jī)制。
- 對(duì)于原生MySQL,在高可用和強(qiáng)一致兩個(gè)特性中,只能二選一:
- 要求MySQL主備間的數(shù)據(jù)強(qiáng)一致,不做主備自動(dòng)切換。
- 借助MHA實(shí)現(xiàn)高可用,容忍MySQL主備間的數(shù)據(jù)不一致。
因此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所示。
圖8 實(shí)現(xiàn)一個(gè)可靠日志存儲(chǔ)保證各MySQL的數(shù)據(jù)一致
【請(qǐng)求透?jìng)鳌?/strong>
在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):
- 需要維護(hù)一個(gè)MySQL Client API的私有版本,維護(hù)成本高。
- 所有的調(diào)用端需要集成這個(gè)私有的MySQL Client API,操作成本很高。
為了避免修改MySQL Client API,可通過增加Proxy進(jìn)行請(qǐng)求透?jìng)鱽斫鉀Q上述問題。在每一個(gè)MySQL結(jié)點(diǎn)上增加一個(gè)Proxy,MySQL Client的請(qǐng)求不再直接訪問MySQL而直接訪問Proxy。Proxy根據(jù)Master的位置,將訪問Slave機(jī)器的請(qǐng)求透?jìng)鞯組aster機(jī)器,再進(jìn)行MySQL操作。
通過增加Proxy進(jìn)行請(qǐng)求透?jìng)?,解決了MySQL Client分裂導(dǎo)致有可能讀取到舊數(shù)據(jù)的問題,如圖9所示。
圖9 實(shí)現(xiàn)一個(gè)可靠日志存儲(chǔ)保證各MySQL的數(shù)據(jù)一致
【自動(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完成以下功能:
- Master機(jī)器的Agent監(jiān)控本機(jī)MySQL是否正常服務(wù);如果正常服務(wù),則定期到可靠存儲(chǔ)延長租約,否則停止續(xù)約。
- 非Master機(jī)器的Agent定期從可靠存儲(chǔ)檢查Master租約是否過期;如果過期,再檢查本機(jī)MySQL是否已經(jīng)執(zhí)行了所有Binlog。如果已經(jīng)執(zhí)行了所有Binlog,則發(fā)起選舉自己為Master的投票,如圖10所示。
圖10 可靠日志存儲(chǔ)和Agent共同實(shí)現(xiàn)自動(dòng)選主機(jī)制
PhxSQL架構(gòu)和實(shí)現(xiàn)
從上述思路可以得出PhxSQL的簡(jiǎn)單三層架構(gòu)。對(duì)于每一個(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é)請(qǐng)求的透?jìng)?。Master結(jié)點(diǎn)上的PhxSync負(fù)責(zé)將MySQL的Binlog發(fā)送到PhxBinlogSvr,如圖11所示。
圖11 PhxSQL基本架構(gòu)
【Proxy(PhxSQLProxy)】
請(qǐng)求透?jìng)?/strong>
請(qǐng)求透?jìng)魇荘roxy主要的功能。主要解決在進(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的請(qǐng)求透?jìng)鞣謨煞N:
- 讀寫端口請(qǐng)求透?jìng)鳎篠lave節(jié)點(diǎn)收到的請(qǐng)求透?jìng)鹘oMaster節(jié)點(diǎn)執(zhí)行。Master節(jié)點(diǎn)收到的請(qǐng)求直接透?jìng)鹘o本機(jī)MySQL執(zhí)行。
- 只讀端口請(qǐng)求透?jìng)鳎篗aster節(jié)點(diǎn)收到的請(qǐng)求透?jìng)鹘oSlave節(jié)點(diǎn)執(zhí)行。Slave節(jié)點(diǎn)收到的請(qǐng)求直接透?jìng)鹘o本機(jī)MySQL執(zhí)行,如圖12所示。
圖12 Proxy請(qǐng)求透?jìng)髁鞒?/p>
高性能:由于Proxy接管了MySQL Client的請(qǐng)求,為了使整個(gè)集群的讀寫性能接近單機(jī)MySQL,Proxy使用協(xié)程模型提高自身的處理能力。
Proxy的協(xié)程模型使用開源的Libco庫。Libco庫是微信團(tuán)隊(duì)開源的一個(gè)高性能協(xié)程庫,具有以下特點(diǎn):
- 用同步方式寫代碼,實(shí)現(xiàn)異步代碼的性能。
- 支持***的并發(fā)連接。
- 項(xiàng)目地址 https://github.com/tencent-wechat/libco
完全兼容MySQL:為了已有的應(yīng)用程序能夠不做任何修改就能遷移到PhxSQL,Proxy需兼容MySQL的所有功能。
兼容MySQL事務(wù)
MySQL事務(wù)管理基于連接,同一個(gè)事務(wù)的所有請(qǐng)求通過同一個(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)斷開,對(duì)應(yīng)的事務(wù)會(huì)被rollback,如圖13所示。
圖13 Proxy的1對(duì)1事務(wù)連接模型
兼容MySQL權(quán)限
MySQL的權(quán)限管理基于(用戶,源IP)對(duì),源IP是通過socket句柄反查獲取。當(dāng)請(qǐng)求通過Proxy連接到MySQL時(shí),源IP為Proxy本地IP,權(quán)限管理會(huì)出現(xiàn)異常。
Proxy利用MySQL協(xié)議HEAD保留字段透?jìng)髡鎸?shí)源IP到MySQ,MySQL再從HEAD保留字段獲取正確的源IP進(jìn)行權(quán)限管理,如圖14所示。
圖14 Proxy通過修改MySQL協(xié)議兼容MySQL權(quán)限
PhxSync
PhxSync的功能和MySQL的semisync插件類似。經(jīng)過調(diào)研,對(duì)semisync插件的接口做少量的調(diào)整,就可以使用這些插件接口來實(shí)現(xiàn)PhxSync。
PhxSync功能主要是:
- 正常運(yùn)行時(shí)提交Binlog:MySQL在正常寫入或者更新數(shù)據(jù)時(shí),會(huì)調(diào)用after_flush接口。PhxSync插件通過實(shí)現(xiàn)after_flush接口將MySQL新寫入的Binlog提交到本機(jī)的BinlogSvr,由本機(jī)BinlogSvr通過Paxos協(xié)議同步到BinlogSvr集群。
- 重啟時(shí)校準(zhǔn)本地Binlog:MySQL在重啟時(shí)通過查詢BinlogSvr集群判斷本地Pending Binlog的狀態(tài)。如果Pending Binlog未復(fù)制到BinlogSvr集群則從本地刪除,保持本地的Binlog數(shù)據(jù)和BinlogSvr集群的Binlog數(shù)據(jù)一致。
由于MySQL沒有提供在重啟時(shí)的插件接口,為了后續(xù)維護(hù)方便,在MySQL代碼層抽象出了一個(gè)新插件接口before_binlog_init用于校準(zhǔn)Binlog。
上述對(duì)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)。
- PhxPaxos庫:BinlogSvr使用PhxPaxos庫進(jìn)行數(shù)據(jù)的復(fù)制。PhxPaxos庫是微信團(tuán)隊(duì)開源的Paxos類庫,具有以下特性:1. 保證各節(jié)點(diǎn)的數(shù)據(jù)一致。
- 保證集群機(jī)器超過一半存活還能服務(wù)。
- 高性能。
- 功能完善。
- 穩(wěn)定性經(jīng)過大規(guī)模驗(yàn)證。
- 接口方便易用。
- 項(xiàng)目地址 https://github.com/tencent-wechat/phxpaxos
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所示。
圖15 BinlogSvr通過Master信息拒絕非Master節(jié)點(diǎn)的提交
防止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通過檢查請(qǐng)求中(本機(jī)MySQL已經(jīng)執(zhí)行的***GTID)和自身保存的***GTID是否匹配來拒絕重新發(fā)送或者異常發(fā)送的數(shù)據(jù),如圖16所示。
圖16 BinlogSvr使用樂觀鎖拒絕Master在數(shù)據(jù)異常的情況下提交數(shù)據(jù)
- 支持MySQL原生復(fù)制協(xié)議:為了讓Slave能從BinlogSvr獲取Binlog,***的方式就是BinlogSvr支持MySQL原生的復(fù)制協(xié)議,這樣不用對(duì)Slave做任何修改,如圖17所示。
圖17 BinlogSvr支持MySQL使用原生復(fù)制協(xié)議獲取Binlog數(shù)據(jù)
- 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所示。
圖18 PhxSQL 3機(jī)數(shù)據(jù)對(duì)比
【Master自動(dòng)切換】
通過觀察Master宕機(jī)時(shí)各節(jié)點(diǎn)的流量變化判斷Master是否順利切換。下圖中的紅線代表流量。當(dāng)Master宕機(jī)時(shí),流量會(huì)隨之轉(zhuǎn)移,代表Master順利切換,如圖19所示。
圖19 PhxSQL進(jìn)行Master切換時(shí)各節(jié)點(diǎn)的寫入流量變化
【PhxSQL性能】
MySQL版本:Percona 5.6.31-77.0
機(jī)器信息:
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ù):
- sysbench。
- –oltp-tables-count=10 –oltp-table-size=1000000 –num-threads=500。
- –max-requests=100000 –report-interval=1 –max-time=200。
PhxSQL的寫性能比MySQL的半同步好,讀性能由于多了一層Proxy導(dǎo)致比MySQL的半同步稍差。
圖20 PhxSQL和MySQL的性能對(duì)比