《微信開源PhxSQL背后:強(qiáng)一致高可用分布式數(shù)據(jù)庫(kù)的設(shè)計(jì)和實(shí)現(xiàn)哲學(xué)》要點(diǎn):
本文介紹了微信開源PhxSQL背后:強(qiáng)一致高可用分布式數(shù)據(jù)庫(kù)的設(shè)計(jì)和實(shí)現(xiàn)哲學(xué),希望對(duì)您有用。如果有疑問,可以聯(lián)系我們。
老司機(jī)簡(jiǎn)介
PhxSQL開源地址見:
https://github.com/tencent-wechat/phxsql
PhxSQL是一個(gè)兼容MySQL、服務(wù)高可用、數(shù)據(jù)強(qiáng)一致的關(guān)系型數(shù)據(jù)庫(kù)集群.PhxSQL以單Master多Slave方式部署,在集群內(nèi)超過(guò)一半機(jī)器存活的情況下、即可提供服務(wù),并且自身實(shí)現(xiàn)自動(dòng)Master切換、保證數(shù)據(jù)一致性.PhxSQL不依賴于Zookeeper等任何第三方做存活檢測(cè)及選主.PhxSQL基于MySQL的一個(gè)分支Percona 5.6開發(fā),功能和實(shí)現(xiàn)與MySQL基本一致.
PhxSQL可以提供和zookeeper相同的強(qiáng)一致性與高可用性,并且支持serializable級(jí)別的事務(wù)隔離.
陳明表示,微信研發(fā)PhxSQL是為了解決在應(yīng)用MySQL時(shí)一個(gè)普遍且巨大的痛點(diǎn):強(qiáng)一致和高可用,MySQL不支持PhxSQL面向的分布式環(huán)境下強(qiáng)一致和高可用應(yīng)用場(chǎng)景,而這在金融和賬號(hào)類關(guān)鍵應(yīng)用是必須的.據(jù)他們所知,目前還未有類似的公開方案,因此,只能自己研發(fā).
后面的正文里面,也會(huì)詳細(xì)的討論MySQL在分布式環(huán)境下的痛點(diǎn).
PhxSQL目前應(yīng)用在微信后臺(tái)的賬號(hào)系統(tǒng)、企業(yè)微信、及QQ郵箱.其中一個(gè)關(guān)鍵服務(wù)的集群由5臺(tái)服務(wù)器構(gòu)成一個(gè)組,容忍兩機(jī)同時(shí)故障,寫高峰2000/秒,讀高峰60000/秒.
在他們的基準(zhǔn)測(cè)試中,PhxSQL相對(duì)于MySQL的寫性能有16-25%的提升,讀性能持平.具體測(cè)試報(bào)告參見 github/phxsql 網(wǎng)址.實(shí)際上,PhxSQL的主要目標(biāo)是強(qiáng)一致、高可用、和serializable事務(wù)隔離;性能只是一個(gè)關(guān)注點(diǎn).另外,PhxSQL將很快支持性能更好的5.7版本MySQL.
PhxSQL設(shè)計(jì)的一個(gè)基本原則就是完全兼容MySQL,MySQL客戶端不需要任何修改就可以直接訪問PhxSQL,以便于將現(xiàn)有的.但是由于MySQL客戶端不支持服務(wù)器故障時(shí)連接到新的可用節(jié)點(diǎn),他們提供了多種可選方案.
有些人可能會(huì)注意到PhxPaxos和PhxSQL都帶有前綴Phx,陳明說(shuō)Phx代表不死的鳳凰,是鳳凰英文單詞phoenix的縮寫.它將用于微信后端的一系列開源項(xiàng)目,后續(xù)他們將放出更多的開源項(xiàng)目,敬請(qǐng)期待.
PhxSQL發(fā)布以來(lái),受到很多關(guān)注.作為熱愛技術(shù)的碼農(nóng),我們感謝大家的關(guān)心和支持,歡迎一切基于技術(shù)出發(fā)點(diǎn)的討論.“Show you the code”之后,我們?cè)谶@里談?wù)凱hxSQL的設(shè)計(jì)和實(shí)現(xiàn)哲學(xué),也同時(shí)回答大家提出的一些疑問.
PhxSQL是一個(gè)通過(guò)Paxos保證強(qiáng)一致和高可用的的MySQL集群.PhxSQL建立在Paxos的一致性和MySQL的binlog流水基礎(chǔ)上.主要原理簡(jiǎn)單來(lái)說(shuō):
只要有多于一半機(jī)器工作和互聯(lián),PhxSQL就可以正常工作.
圖 1:PhxSQL架構(gòu)
很多MySQL集群方案都宣稱強(qiáng)一致和高可用.PhxSQL這方面有什么不同?
大家熟知的Zookeeper提供強(qiáng)一致和高可用.一致性有很多級(jí)別,從強(qiáng)到弱分別是:Strict(嚴(yán)格一致性),Linearizable(線性一致性),Sequential(序列一致性),Causal(因果一致性),Eventual等.
嚴(yán)格一致性只是一個(gè)理論模型.根據(jù)相對(duì)論,由于信息傳播的速度不可能高于光速,嚴(yán)格一致性在實(shí)際中幾乎無(wú)法實(shí)現(xiàn).線性一致性的理論定義很復(fù)雜,不太嚴(yán)謹(jǐn)直觀地講,就是任何一個(gè)客戶端都可以讀到別的客戶端寫入的最新內(nèi)容.這也是大家通常理解的強(qiáng)一致.
Zookeeper的強(qiáng)一致指“線性一致性”.高可用是說(shuō)只要多于一半機(jī)器工作和互聯(lián)即可在保證線性一致性的質(zhì)量下正常工作.PhxSQL的強(qiáng)一致是指“線性一致性”,高可用是指只要多于一半機(jī)器工作和互聯(lián)即可在保證線性一致性質(zhì)量下工作.即,
PhxSQL提供和Zookeeper相同的強(qiáng)一致性和高可用性!
PhxSQL提供和Zookeeper相同的強(qiáng)一致性和高可用性!
PhxSQL提供和Zookeeper相同的強(qiáng)一致性和高可用性!
重要事情說(shuō)三遍:).大家可以把PhxSQL當(dāng)Zookeeper使用,例如用來(lái)選主!
在數(shù)據(jù)庫(kù)事務(wù)隔離方面,PhxSQL支持最高級(jí)別的serializable.在性能方面,PhxSQL提供明顯優(yōu)于MySQL半同步的寫性能和幾乎相同的讀性能[22].
細(xì)心的讀者可能注意到,和通常的“高可用、強(qiáng)一致”說(shuō)法順序相反,這個(gè)小節(jié)的標(biāo)題中,“強(qiáng)一致”有意放在了“高可用”的前面.在這里需要澄清一個(gè)誤區(qū).當(dāng)談到高可用時(shí),有時(shí)會(huì)有意無(wú)意忽略或者降低一致性.
嚴(yán)格意義上來(lái)講,可用性和一致性必須一塊討論,滿足一致性要求前提下的可用性才有意義.打個(gè)不嚴(yán)謹(jǐn)?shù)谋确?一致性就像汽車的“安全性”,可用性就是汽車的“可用性”.如果一輛車號(hào)稱“可用性”很好,可以連續(xù)工作10年,但從來(lái)不提“安全性”,那么這輛車的品質(zhì)是值得懷疑的.
雖然有很多NoSQL、NewSQL系統(tǒng)、以及多主多寫MySQL集群、支持分庫(kù)分表的MySQL集群,MySQL傳統(tǒng)主備同步方案仍然具備很多新系統(tǒng)難以企及的優(yōu)點(diǎn).MySQL主備在主機(jī)上支持完整SQL、全局事務(wù)、以repeatable read和serializable級(jí)別的事務(wù)隔離,在金融、帳號(hào)等關(guān)鍵業(yè)務(wù)中有巨大的價(jià)值.同時(shí),現(xiàn)有復(fù)雜MySQL應(yīng)用遷移到新的非兼容系統(tǒng)成本也很高.
但是MySQL傳統(tǒng)主備方案也有其缺點(diǎn).最明顯的就是主機(jī)故障后的自動(dòng)換主和新舊主數(shù)據(jù)一致性(以及衍生的換主后主備一致性、各個(gè)備機(jī)之間一致性,這里就不詳加討論了),即所謂的一致性和可用性.
為了解決這個(gè)問題,有傳統(tǒng)流派:用Zookeeper、etcd、或者其它第三方來(lái)檢測(cè)心跳、選主、和切換.改造MySQL client讓其能感知新的主機(jī).或者為了能讓傳統(tǒng)MySQL client不加修改就能感知新的主機(jī),部署MySQL代理服務(wù)器,讓其將連到自身的MySQL client請(qǐng)求透明轉(zhuǎn)發(fā)給新的主機(jī).
為了減少主備間數(shù)據(jù)的落后,從而降低舊主機(jī)故障、某臺(tái)備機(jī)被提升成新主機(jī)時(shí),新舊主機(jī)之間、新主機(jī)和其它備機(jī)之間的差異,很多方案在主備同步機(jī)制上做了很多有益的工作.
例如semi-sync等待多數(shù)派備機(jī)應(yīng)答,通過(guò)優(yōu)化線程和網(wǎng)絡(luò)、主備多通道、備機(jī)并行執(zhí)行binlog流水等盡量減少主備之間差異.如果主備間任何時(shí)刻都完全一致,那么任何時(shí)刻換主都是強(qiáng)一致的.這句話的另外一個(gè)意思是,如果無(wú)法保證主備間任何時(shí)刻完全一致,那么當(dāng)有持續(xù)不斷的更新時(shí),任何時(shí)刻的換主都是無(wú)法保證強(qiáng)一致的.
傳統(tǒng)流派另外一個(gè)分支就是將MySQL本地磁盤換成更高可靠性的SAN,當(dāng)MySQL主機(jī)故障時(shí),將SAN掛接到備機(jī)上提供服務(wù).而當(dāng)SAN故障時(shí)怎么辦?當(dāng)需要跨機(jī)房部署時(shí)怎么辦?
意識(shí)到傳統(tǒng)流派的局限性,新出了Galera和MySQL Group Replication等.除了宣稱提供強(qiáng)一致性和高可用性外,還支持master-master多點(diǎn)寫入等誘人新特性.
世界上目前已知的經(jīng)過(guò)理論證明和實(shí)際檢驗(yàn)的一致性算法屈指可數(shù):兩階段提交two-phase commit (2PC)、Paxos、Raft、Zookeeper Atomic Broadcast (ZAB)、Viewstamped Replication等.2PC雖然可以保證一致性,但在主機(jī)故障時(shí)無(wú)法工作,存在可用性問題.
目前被工業(yè)界廣泛認(rèn)可和應(yīng)用的是Paxos和Raft,2PC一般在前兩者幫助選主下使用.這里的一個(gè)小建議就是:如果一個(gè)分布式系統(tǒng)宣稱支持線性一致性級(jí)別的強(qiáng)一致和高可用,請(qǐng)先檢查它使用的一致性算法.如果是新算法,請(qǐng)檢查它的形式化證明或者邏輯證明.
因此,為支持線性一致性和高可用,同時(shí)完全兼容MySQL,我們?cè)贛ySQL的基礎(chǔ)上應(yīng)用Paxos,設(shè)計(jì)和開發(fā)了PhxSQL.
從實(shí)際需求出發(fā),除了前述強(qiáng)一致、高可用、完全兼容MySQL這3個(gè)明顯、必須的設(shè)計(jì)原則,我們還提出以下3原則.
1、簡(jiǎn)單可邏輯證明的一致性模型
這可能是明顯區(qū)別PhxSQL和其它方案的一個(gè)特點(diǎn).一個(gè)經(jīng)過(guò)邏輯證明的模型才是可靠的,一個(gè)建立在可靠模型基礎(chǔ)上的系統(tǒng)也才是可信賴的.PhxSQL一致性模型建立在兩個(gè)前提上:
在這兩個(gè)前提下,PhxSQL的一致性模型通過(guò)Paxos,使得主機(jī)寫入Paxos的binlog流水與備機(jī)從Paxos里拉取的binlog流水一致,從而保證MySQL數(shù)據(jù)的一致性.詳細(xì)證明過(guò)程我們將另外提供.模型和證明過(guò)程都很簡(jiǎn)單,大家在讀完源碼后也可以嘗試:).
但即使模型正確,PhxSQL正確實(shí)現(xiàn)了這個(gè)模型嗎?用通俗的話講,沒有bug.碼農(nóng)都知道這是個(gè)巨大的挑戰(zhàn).從一個(gè)算法和模型到正確的實(shí)現(xiàn)之間差距是巨大的.例如,正確實(shí)現(xiàn)Paxos挑戰(zhàn)就很大.為了盡量減少bug,我們選擇了簡(jiǎn)單和易理解同時(shí)應(yīng)用廣泛的Paxos作為一致性協(xié)議.
為了實(shí)現(xiàn)一個(gè)“生產(chǎn)”級(jí)別的Paxos,三位主要碼農(nóng)各自獨(dú)立實(shí)現(xiàn)了Paxos,在各自測(cè)試完正確性后,三套Paxos之間作為一組Paxos的獨(dú)立節(jié)點(diǎn)互操作檢驗(yàn)正確性,最后再集體實(shí)現(xiàn)一個(gè)發(fā)行版本PhxPaxos!除了單元測(cè)試、系統(tǒng)測(cè)試外,測(cè)試環(huán)境還隨機(jī)高頻率獨(dú)立重啟機(jī)器、對(duì)網(wǎng)絡(luò)包進(jìn)行亂序、延遲、重復(fù)等以對(duì)系統(tǒng)進(jìn)行充分測(cè)試.
有讀者可能問,Paxos慢且網(wǎng)絡(luò)延遲大,PhxSQL為什么不實(shí)現(xiàn)一個(gè)“優(yōu)化”版?Paxos有各種版本,例如Fast Paxos、EPaxos.但這些都是Paxos,遵循Paxos的基本操作,所作的改變,都做了嚴(yán)格的形式化或者邏輯證明.
PhxPaxos嚴(yán)格遵照Paxos算法實(shí)現(xiàn),沒有做任何改變或者“優(yōu)化”.PhxPaxos的正常寫操作的網(wǎng)絡(luò)延遲是一個(gè)網(wǎng)絡(luò)RTT,已經(jīng)是任何算法理論上能達(dá)到的最快速度.
現(xiàn)在,對(duì)于PhxSQL來(lái)說(shuō),切換主機(jī)是一件很平常和容易的操作,就像往MySQL里插入一條數(shù)據(jù)一樣平常和容易.
2、最小侵入MySQL原則
MySQL是個(gè)巨大的快速演進(jìn)的生態(tài)系統(tǒng),保證PhxSQL中的MySQL與官方MySQL的兼容性與可升級(jí)性是必須的.這使得MySQL應(yīng)用可以快速遷移到和運(yùn)行在PhxSQL上.這也使得PhxSQL可以迅速響應(yīng)官方MySQL的升級(jí),將PhxSQL中的舊版本MySQL升級(jí)到新的官方MySQL,從而獲得新的特性、性能、穩(wěn)定性、和安全性提升.
這就要求PhxSQL中的MySQL對(duì)官方MySQL改動(dòng)盡量少.實(shí)際上,PhxSQL版MySQL只更改了三個(gè)小地方:修改了binlog插件接口中一個(gè)函數(shù)的參數(shù);在MySQL啟動(dòng)時(shí)新增了一個(gè)插件函數(shù)用于檢查MySQL本地的binlog文件,在MySQL協(xié)議中透?jìng)髁苏嬲目蛻舳薎P以兼容授權(quán)功能(如果應(yīng)用不需要可以不修改).
這幾個(gè)改動(dòng)是如此小,且涉及的是幾乎穩(wěn)定不變的流程,使得PhxSQL中MySQL跟隨官方MySQL升級(jí)可以無(wú)縫完成.實(shí)際上,我們正和MySQL社區(qū)溝通,希望把這幾處修改并入官方版本,從而使得PhxSQL以后可以完全使用官方版本MySQL,也使得更多人可以方便采用PhxSQL.
(a)
(b)
圖 2:PhxSQL對(duì)MySQL的關(guān)鍵修改.(a)是MySQL.(b)是PhxSQL中的MySQL.PhxSQL修改了after_flush這個(gè)函數(shù)的參數(shù),新增了在MySQL啟動(dòng)時(shí)調(diào)用的before_recovery這個(gè)函數(shù).
也正是因?yàn)檫@個(gè)原因,我們沒有因?yàn)榭梢詼p少模塊個(gè)數(shù)而把PhxSQLProxy、PhxBinlogSvr放到MySQL進(jìn)程內(nèi).為了保證和應(yīng)用最廣泛的MySQL單機(jī)版兼容,也沒有在事務(wù)層和存儲(chǔ)層介入或修改.
3、簡(jiǎn)單的架構(gòu)、部署、和運(yùn)維
在滿足要求的前提下,簡(jiǎn)單的架構(gòu)有很多好處,例如開發(fā)、維護(hù)、診斷、維護(hù)、可靠性等等都變得容易.PhxSQL只有3+1個(gè)模塊.PhxSQL中的MySQL是必須的.PhxBinlogSvr負(fù)責(zé)全局binlog存儲(chǔ)和同步、選主、集群成員管理等關(guān)鍵功能.
在很多MySQL主備集群方案中,使用Zookeeper、etcd、心跳檢測(cè)、Agent等承擔(dān)選主的功能.PhxSQLProxy則作為傳統(tǒng)MySQL client訪問PhxSQL中MySQL服務(wù)的代理,使得PhxSQL的換主操作對(duì)于傳統(tǒng)MySQL client透明.
在其它集群方案中,一般也是通過(guò)代理MySQL proxy、或者虛擬網(wǎng)關(guān)VIP,向傳統(tǒng)MySQL client屏蔽集群的換主操作,提供透明訪問.可選模塊是PhxSQL client lib,它修改了MySQL client庫(kù)中的連接初始化函數(shù),允許傳入一個(gè)集群的PhxSQLProxy列表,從而在一個(gè)PhxSQLProxy沒有響應(yīng)時(shí)、自動(dòng)訪問其它可用PhxSQLProxy,進(jìn)一步提高可用性.開發(fā)人員可以直接鏈接PhxSQL client lib.
PhxSQL的部署和運(yùn)維都很簡(jiǎn)單.在部署時(shí),只要在目標(biāo)機(jī)各自裝好PhxSQL,在配置中指定集群的機(jī)器的IP列表,PhxSQL即可運(yùn)行.換主這個(gè)操作已經(jīng)從運(yùn)維層面轉(zhuǎn)移到PhxSQL正常的工作流程.
通常MySQL集群中換主后可能需要人肉檢查數(shù)據(jù)一致性、人肉“閃回”(這可能違反一致性保證,導(dǎo)致“幻讀”);無(wú)法”閃回“導(dǎo)致必須清除某臺(tái)MySQL,重新拉取數(shù)據(jù)備份,和追流水等.這些耗時(shí)、繁重、易錯(cuò)的操作在PhxSQL中已經(jīng)完全不需要.
至于熱升級(jí)和熱變更集群成員更簡(jiǎn)單.PhxSQL支持rolling-update,可以逐步升級(jí)每臺(tái)機(jī)器.變更集群成員是指往集群添加機(jī)器、撤出機(jī)器、和替換機(jī)器(原子操作).在保證強(qiáng)一致和高可用前提下,熱變更(不停服變更)是非常困難的.
PhxSQL通過(guò)在Paxos中實(shí)現(xiàn)成員變更解決了這個(gè)難題.PhxSQL提供了一個(gè)變更操作命令.當(dāng)在新機(jī)器安裝和啟動(dòng)PhxSQL后(要求MySQL已經(jīng)加載一份較新的數(shù)據(jù)備份),可以在現(xiàn)有集群中一鍵將新機(jī)器引入、剔除一臺(tái)舊機(jī)器、或者同時(shí)做兩者.這三種操作都是原子操作!新機(jī)器會(huì)自動(dòng)追流水.想想看,相當(dāng)于Zookeeper支持熱變更成員,是不是很令人激動(dòng)的特性?
PhxSQL由于在同步層使用Paxos,天然支持多數(shù)據(jù)中心、多機(jī)房部署,兩地三中心這種部署更是不在話下.對(duì)于PhxSQL來(lái)說(shuō),多數(shù)據(jù)中心和多機(jī)房部署與機(jī)房?jī)?nèi)部署沒有區(qū)別.PhxSQL的性能取決于多數(shù)派機(jī)器之間的網(wǎng)絡(luò)延遲.
作為熱愛技術(shù)的碼農(nóng),我們相信開源的技術(shù)可以使得這個(gè)世界更美好.從我們?nèi)粘i_發(fā)使用的Emacs/Vim、GCC、GDB,間接為大眾提供社交、電子商務(wù)、信息服務(wù)的Linux、Apache、MySQL、PHP,到大眾每日使用來(lái)溝通和娛樂的Android等,開源是整個(gè)互聯(lián)網(wǎng)的基石,為全世界提供許多關(guān)鍵不可或缺的基礎(chǔ)服務(wù).我們充分享受了開源帶來(lái)的技術(shù)進(jìn)步、經(jīng)濟(jì)發(fā)展、和社會(huì)前進(jìn),我們也希望開源的PhxSQL可以回饋社區(qū),幫助更多有需要的人.
另外,我們希望通過(guò)開源更好地改進(jìn)PhxSQL.我們歡迎技術(shù)性討論和志愿者提交修改.我們承諾開源的PhxSQL會(huì)一直更新.除了一些和內(nèi)部運(yùn)維支撐系統(tǒng)進(jìn)行集成的功能(PhxSQL把這些功能抽象成插件,我們針對(duì)內(nèi)部運(yùn)維支撐系統(tǒng)實(shí)現(xiàn)了這些插件),開源版和內(nèi)部版本將保持一致.
在一個(gè)不完美的世界里,完美是不存在的.我們很坦誠(chéng)指出PhxSQL存在的兩個(gè)局限:
1、MySQL主機(jī)在執(zhí)行SQL DDL命令(例如建庫(kù)和建表命令)時(shí)可能存在一致性風(fēng)險(xiǎn).
由于MySQL的innodb引擎不支持DDL回滾,如果主機(jī)在innodb已經(jīng)commit這條DDL命令,但是這條命令的binlog還沒到達(dá)PhxSQL的攔截點(diǎn)前宕機(jī),則這條DDL binlog會(huì)在全局binlog中缺失,從而備機(jī)也不會(huì)收到這條binlog.
而為了保證線性一致性、serializable級(jí)別事務(wù)隔離、及“最小侵入MySQL”原則,我們也不想修改MySQL源碼,提前截獲DDL命令.考慮到DDL命令頻度較低,我們后續(xù)準(zhǔn)備在PhxSQLProxy加入檢查和后續(xù)審計(jì)告警.也歡迎大家提出更好方案.
2、在寫入請(qǐng)求量很大的系統(tǒng)中,MySQL備機(jī)流水可能落后較多;如果這個(gè)時(shí)候主機(jī)死機(jī),備機(jī)暫時(shí)無(wú)法提升成新主機(jī),造成系統(tǒng)在一段時(shí)間內(nèi)不可寫.
為了保證線性一致性,對(duì)于要求讀取最新數(shù)據(jù)的請(qǐng)求(通過(guò)ReadWritePort發(fā)起的讀請(qǐng)求)也將失敗;需要等至少一臺(tái)備機(jī)追完流水,被提升為主機(jī)才能響應(yīng)讀取最新數(shù)據(jù)的請(qǐng)求.
對(duì)于不需要讀取最新數(shù)據(jù)的請(qǐng)求(通過(guò)ReadonlyPort發(fā)起的請(qǐng)求),可以從任意備機(jī)執(zhí)行,但不保證線性一致性.(注意:PhxSQL保證無(wú)論MySQL主機(jī)流水領(lǐng)先MySQL備機(jī)多少,MySQL主機(jī)binlog流水和全局binlog流水是一致的,不會(huì)導(dǎo)致數(shù)據(jù)丟失和破壞線性一致性.)
MySQL備機(jī)追流水落后是基于binlog復(fù)制這種模式的一個(gè)潛在問題.事實(shí)上,不僅MySQL主備,任何一個(gè)多副本系統(tǒng),只要每個(gè)寫操作不等待所有副本返回,都會(huì)出現(xiàn)類似的有些副本落后的問題;而那些等待所有副本返回的模式,在耗時(shí)和可用性方面又存在問題.
可喜的是MySQL 5.7版本實(shí)現(xiàn)了并行復(fù)制機(jī)制,顯著地提高了備機(jī)追流水的性能.PhxSQL將很快支持MySQL 5.7,對(duì)于寫入請(qǐng)求量很大的場(chǎng)景也可以很大程度上避免備機(jī)追流水落后的情況.
1、為什么不支持多寫?
多寫想想就很誘人.多寫可以充分利用每臺(tái)機(jī)器寫時(shí)需要的資源.例如某些寫操作可能非常耗費(fèi)CPU,多寫可以把寫操作分散在各臺(tái)機(jī)器上,充分利用各個(gè)機(jī)器的CPU資源,極大提高寫入的性能.多寫使得換主沒有存在的必要,也就沒有換主時(shí)可能存在的不可寫時(shí)間窗問題.多寫還使得客戶端可以就近寫入,減少跨數(shù)據(jù)中心寫入帶來(lái)的網(wǎng)絡(luò)延遲.
多寫有兩種:大家熟知的分shard或者組,各shard或者組間并行寫入,以Google Spanner[8]為典型代表;在shard或者組內(nèi)并行,以Galera和MySQL Group Replication為代表.
1.組間多寫
組間多寫是把數(shù)據(jù)分成多個(gè)不相交的shard,每個(gè)組的機(jī)器負(fù)責(zé)一個(gè)shard .當(dāng)一個(gè)事務(wù)涉及的數(shù)據(jù)(讀集合和或?qū)懠?都在某個(gè)組時(shí),這種事務(wù)稱為本地事務(wù).當(dāng)一個(gè)事務(wù)涉及的數(shù)據(jù)分布在超過(guò)一個(gè)組時(shí),這種事務(wù)稱為分布式事務(wù).
本地事務(wù)可以在本組獨(dú)立執(zhí)行,組之間不需要任何通信.為了減少事務(wù)沖突帶來(lái)的性能降低,一般都是由組內(nèi)leader執(zhí)行本地事務(wù),通過(guò)Paxos等一致性協(xié)議保證組內(nèi)機(jī)器的數(shù)據(jù)一致[8].各個(gè)組間并行執(zhí)行本地事務(wù),可以極大提高本地型事務(wù)的寫性能.
組間多寫最大的阻礙是分布式事務(wù),而分布式事務(wù)是非常昂貴的.在SQL的模型中,為了實(shí)現(xiàn)read repeatable級(jí)別的事務(wù)隔離,事務(wù)管理器需要檢查兩個(gè)并發(fā)事務(wù)的寫數(shù)據(jù)集是否沖突;為了達(dá)到serializable級(jí)別的事務(wù)隔離,事務(wù)管理器需要檢查兩個(gè)并發(fā)事務(wù)的讀數(shù)據(jù)集和寫事務(wù)集是否沖突.
這一般通過(guò)嚴(yán)格兩階段鎖(strict two-phase locking,嚴(yán)格2PL)和/或者多版本并發(fā)控制MVCC實(shí)現(xiàn).當(dāng)這些數(shù)據(jù)集跨組時(shí),就涉及到跨組的機(jī)器通信.
一個(gè)組同時(shí)遇到本地事務(wù)和分布式事務(wù)時(shí),在本組需要根據(jù)事務(wù)的隔離級(jí)別,由事務(wù)管理器仲裁執(zhí)行.
以Google Spanner為例,一個(gè)涉及兩個(gè)機(jī)器組(Spanner中的組是指Paxos組)事務(wù)就需要在coordinator leader和non-coordinator-participant leader之間兩次通信,前者組內(nèi)還涉及一次Paxos寫操作,后者組內(nèi)再加兩次Paxos寫操作[8,Sec. 4.2.1 Read-Write Transactions].
當(dāng)跨機(jī)房部署時(shí),機(jī)器之間的網(wǎng)絡(luò)延遲使得通信代價(jià)更加高昂.Spanner為了減少這種昂貴的跨組事務(wù),要求所有數(shù)據(jù)都必須有Primary key,并且其它數(shù)據(jù)盡量掛接在Primary key下面,使得事務(wù)盡量在一個(gè)組內(nèi)、且由組內(nèi)leader執(zhí)行.
2.組內(nèi)多主多寫
組內(nèi)多主多寫時(shí)每個(gè)機(jī)器都有完整的數(shù)據(jù),但這份數(shù)據(jù)分成不相交的邏輯集合,每個(gè)機(jī)器負(fù)責(zé)一個(gè)集合的寫入.這臺(tái)機(jī)器稱為這個(gè)集合的主機(jī),這個(gè)集合稱為這個(gè)主機(jī)負(fù)責(zé)的數(shù)據(jù),其它機(jī)器稱為這個(gè)集合的備機(jī).
客戶端將寫操作發(fā)到所涉及數(shù)據(jù)的主機(jī),由主機(jī)通過(guò)atomic broadcast原子廣播將更新請(qǐng)求發(fā)送給組內(nèi)所有的機(jī)器,包括主機(jī)本身[9].Galera和MySQL Group Replication都是采用這種方法.
圖 3:組內(nèi)多主多寫架構(gòu)[9]
原子廣播具有3個(gè)特性:
使用原子廣播后,事務(wù)的生命周期從prepare->committed/aborted改變?yōu)閜repare->committing->committed/aborted.
圖 4:事務(wù)生命周期狀態(tài)轉(zhuǎn)換
當(dāng)一個(gè)事務(wù)只涉及到一個(gè)集合的數(shù)據(jù)時(shí),稱為本地事務(wù),由這個(gè)集合的主機(jī)的本地事務(wù)管理器先使用本地嚴(yán)格2PL仲裁,然后將committing狀態(tài)的事務(wù)通過(guò)原子廣播發(fā)給其它備機(jī).
當(dāng)一個(gè)事務(wù)涉及到多于一個(gè)集合的數(shù)據(jù)時(shí),稱為復(fù)合事務(wù)(complex transaction).
這個(gè)事務(wù)所涉及的某個(gè)數(shù)據(jù)集合的主機(jī)將committing事務(wù)狀態(tài),包括讀集合(如果需要serializable級(jí)別隔離.這里的一個(gè)小優(yōu)化是采用dummy row減少可能極其龐大的讀集合)、寫集合、以及committing狀態(tài),通過(guò)原子廣播發(fā)給全組進(jìn)行仲裁.Galera和MySQL Group Replication都只是校驗(yàn)寫集合,因此不支持serializable級(jí)別事務(wù)隔離.
每臺(tái)機(jī)器都可以通過(guò)本地事務(wù)狀態(tài)和原子廣播收到的消息,獨(dú)立判定committing事務(wù)最終是提交還是終止.這種判定由原子廣播的特性保證全局一致.
圖 5:Deferred Update Replication和Certification-based Replication事務(wù)執(zhí)行時(shí)序
強(qiáng)調(diào)一下:在機(jī)器通過(guò)原子廣播進(jìn)行數(shù)據(jù)同步時(shí),事務(wù)的最終結(jié)果不能在廣播前決定,而是在執(zhí)行這條消息依賴的前置消息及這條消息后才能決定.這稱為Deferred Update Replication推遲的更新復(fù)制或者Certification-based Replication.
這里有個(gè)重要特點(diǎn)需要注意:只有收到一條消息的所有前置消息后,這條消息和所有未執(zhí)行的前置消息才能由事務(wù)管理器并發(fā)執(zhí)行.因此,這里引入了一定的串行化.
原子廣播的突出優(yōu)點(diǎn)是在低延遲局域網(wǎng)有很高的吞吐率.
但同時(shí)原子廣播有不小的按機(jī)器個(gè)數(shù)放大的網(wǎng)絡(luò)延遲,在非低延遲網(wǎng)絡(luò)會(huì)顯著放大網(wǎng)絡(luò)延遲.MySQL Group Replication使用的Corosync和Galera支持的Spread都是基于Totem這個(gè)成員管理和原子廣播協(xié)議.
Totem是個(gè)為低延遲局域網(wǎng)設(shè)計(jì)的協(xié)議.在Totem中,所有機(jī)器組成一個(gè)環(huán)(ring).無(wú)論一臺(tái)機(jī)器是否需要廣播,令牌(token)在機(jī)器之間都按照環(huán)順序傳遞.只有拿到令牌的機(jī)器才可以進(jìn)行廣播,即發(fā)出提交事務(wù)請(qǐng)求.因此在下一臺(tái)機(jī)器收到上一臺(tái)機(jī)器令牌的網(wǎng)絡(luò)延遲期間,整個(gè)系統(tǒng)處于等待狀態(tài).
為了保證Safe Order Delivery,消息(實(shí)際是regular token,不是regular message)需要在環(huán)中循環(huán)兩圈,才能知道是否可以執(zhí)行,因此,消息延遲是(4f+3)*RTT/2,其中(2f+1)是機(jī)器的數(shù)目、f表示容錯(cuò)的機(jī)器數(shù).
在一個(gè)典型的兩地三中心部署中,這導(dǎo)致一次事務(wù)寫操作延遲極其高昂.例如一地兩中心的網(wǎng)絡(luò)延遲一般可以控制在2ms(單向),上海-深圳間網(wǎng)絡(luò)延遲一般是15ms(單向),則一次事務(wù)寫操作的網(wǎng)絡(luò)延遲是64ms!
在兩地三中心的配置中,PhxSQL的主和一個(gè)備一般分別在一地的兩中心,另一個(gè)在異地.在通常情況下,master的Paxos一次寫入只需一個(gè)accept,并且只等最快的備機(jī)返回.這時(shí)PhxSQL的寫延遲只有4ms!
相比Paxos這類協(xié)議,原子廣播還有一個(gè)缺陷.當(dāng)任意一臺(tái)機(jī)器宕機(jī)或者網(wǎng)絡(luò)中斷時(shí),Totem此時(shí)會(huì)超時(shí),在踢掉宕機(jī)的機(jī)器、重新確定組成員之前,整個(gè)集群的消息停止執(zhí)行,即寫操作暫停.
對(duì)于read-only事務(wù),只有去數(shù)據(jù)集合的主機(jī)讀取、或者昂貴地讀取原子廣播Quorum臺(tái)機(jī)器、或者使用類似Spanner的TrueTime技術(shù)讀取任一符合資格的機(jī)器,才能保證線性一致性.Galera節(jié)點(diǎn)間有延遲,并且只讀事務(wù)在本地執(zhí)行,不支持線性一致性.如果MySQL Group Replication支持線性一致性,請(qǐng)不吝告知.
了解基于原子廣播的組內(nèi)多主多寫模式的原理和優(yōu)缺點(diǎn)后,使用多寫模式還需要根據(jù)業(yè)務(wù)仔細(xì)劃分?jǐn)?shù)據(jù)集,盡量減少公共數(shù)據(jù)的使用,同時(shí)處理好自增key的細(xì)節(jié)問題,以減少事務(wù)間的跨機(jī)沖突.
PhxSQL建立在開源的PhxPaxos基礎(chǔ)上,感興趣的讀者可以用PhxPaxos方便實(shí)現(xiàn)原子廣播插件,加載到MySQL中,從而支持多寫.
如果不要read repeatable或者serializable級(jí)別隔離的事務(wù),例如簡(jiǎn)單的key-value操作,同時(shí)通過(guò)lease機(jī)制保證線性一致性,是可以做到高效率多寫的.但這就違反了PhxSQL完全兼容MySQL和最小侵入MySQL的原則.
2、為什么不支持分庫(kù)分表?
分庫(kù)分表也是個(gè)誘人的選擇:可以平行無(wú)限擴(kuò)展讀寫性能.分庫(kù)分表就是分組,上個(gè)小節(jié)已經(jīng)討論了分布式事務(wù)的高昂成本.另外,為了保證完全兼容MySQL、支持全局事務(wù)和serializable級(jí)別事務(wù)隔離,不大改MySQL就支持sharding是非常困難的.
大改又違反了“最小侵入MySQL”原則,并且可能引入新的不兼容性.在應(yīng)用不要求全局事務(wù)和serializable級(jí)別事務(wù)隔離情況下,感興趣的讀者可以把PhxSQL作為一容錯(cuò)的MySQL模塊,在上層構(gòu)建支持分庫(kù)分表的系統(tǒng).因?yàn)镻hxSQL本身的容錯(cuò)性,這樣做比在MySQL基礎(chǔ)上直接構(gòu)建要簡(jiǎn)單,無(wú)需關(guān)心每個(gè)sharding本身的出錯(cuò).
如果以后有需求,PhxSQL團(tuán)隊(duì)也可能基于PhxSQL開發(fā)一個(gè)分庫(kù)分表的新產(chǎn)品.當(dāng)然,這個(gè)產(chǎn)品難以提供PhxSQL級(jí)別的兼容性.
3、為什么這么糾結(jié)于serializable級(jí)別事務(wù)隔離性,read repeatable級(jí)別很多時(shí)候已經(jīng)夠用了啊?
我們?cè)谠O(shè)計(jì)原則中已經(jīng)提到,為了完全兼容MySQL.我們認(rèn)為一項(xiàng)好的技術(shù)是一項(xiàng)簡(jiǎn)單方便用戶的技術(shù),提供符合用戶直覺預(yù)期、不用看太多注意事項(xiàng)的技術(shù)是我們的體貼.我們很誠(chéng)懇,也是為了方便用戶.我們不想說(shuō)PhxSQL完全兼容MySQL,然后在不起眼的地方blabla列出好幾頁(yè)蠅頭小字的例外.事實(shí)上,對(duì)于關(guān)鍵業(yè)務(wù)來(lái)說(shuō),serializable是必要的、read repeatable是不足的.read repeatable有個(gè)令人討厭的write-skew異常[12].
舉個(gè)例子.小薇在一個(gè)銀行有兩張信用卡,分別是A和B.銀行給這兩張卡總的信用額度是2000,即A透支的額度和B透支的額度相加必須不大于2000:A+B<=2000.
兩個(gè)賬戶的扣款函數(shù)用事務(wù)執(zhí)行分別是:
A賬戶扣款函數(shù):
sub_A(amount_a):
begin transaction
if (A+B+amount_a <= 2000)
{ A += amount_a }
Commit
B賬戶扣款函數(shù):
sub_B(amount_b):
begin transaction
if (A+B+amount_b <= 2000)
{ B += amount_b }
commit
假定現(xiàn)在A==1000,B==500.如果小薇是個(gè)黑客,同時(shí)用A賬戶消費(fèi)300和B賬戶消費(fèi)300,即amount_a == 400,amount_b == 300.那么這個(gè)數(shù)據(jù)庫(kù)會(huì)發(fā)生什么事情呢?
如果是read repeatable級(jí)別隔離,sub_a和sub_b都會(huì)同時(shí)成功!最后A和B賬戶的透支額分別是A=1000+400=1400,B=500+300=800,總的透支額A+B=1400+800=2200>2000,超過(guò)了銀行授予的額度!如果不是信用卡的兩筆小消費(fèi),而是兩筆大額轉(zhuǎn)賬,那么銀行怎么辦?
如果是serializable級(jí)別隔離,則sub_a和sub_b只有一個(gè)成功.具體分析有興趣的讀者可以自己完成.
4、為什么不把顯著提升MySQL性能作為一個(gè)主要目標(biāo)?
事實(shí)上,PhxSQL已經(jīng)顯著提升了MySQL主備的寫入性能.與semi-sync比,在測(cè)試環(huán)境中,PhxSQL的寫入性能比semi-sync高15%到20%以上.讀性能持平.這是在滿足完全兼容MySQL和最小侵入MySQL原則下所能獲得的結(jié)果.
鑒于PhxSQL對(duì)MySQL的改動(dòng)是如此之小,對(duì)性能有高要求的讀者,可以方便地把PhxSQL中的MySQL換成其它高性能版本,獲得更高性能.
5、為什么編譯時(shí)不支持C++11以下標(biāo)準(zhǔn)?
作為熱愛新技術(shù)的碼農(nóng),我們真的很喜歡C++11中期待已久、激動(dòng)人心的新特性,例如極大增強(qiáng)的模板、lambda表達(dá)式、右值和move表達(dá)式、多線程內(nèi)存模型等,這將C++帶入了一個(gè)新的時(shí)代,大大提高了碼農(nóng)搬磚的速度、編碼的正確性、和程序的性能.有了C++11,PhxSQL的開發(fā)效率提高了很多.
8、與Galera及MySQL Group replication的比較
參見7.1.2小節(jié).
PhxSQL是一個(gè)完全兼容MySQL,提供與Zookeeper相同強(qiáng)一致性和可用性的MySQL集群.
感謝大家看完這么長(zhǎng)的一篇文章.希望大家多閱讀PhxSQL源碼,多提技術(shù)性意見,甚至成為源碼貢獻(xiàn)者!
文章出處:InfoQ
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.snjht.com/jiaocheng/4414.html