談?wù)凱hxSQL的設(shè)計和實現(xiàn)哲學(xué)(上)
開源地址
https://github.com/tencent-wechat/phxsql
摘要
PhxSQL是一個提供Zookeeper級別強一致和高可用的MySQL集群。PhxSQL完全兼容MySQL,建立在簡單可邏輯證明的一致性模型之上,架構(gòu)、部署、運維簡單。文章還討論了PhxSQL與相關(guān)技術(shù)方案的區(qū)別,并比較了各自的優(yōu)缺點。
文章比較長,正文分成上下兩章。上章主要討論why,即我們?yōu)槭裁匆鯬hxSQL以及為什么這樣做。下章著重探討why not,即我們?yōu)槭裁床恢С秩舾商匦?,例如多主多寫和分庫分表,及與Galera和MySQL Group Replication的比較等。
正文
PhxSQL[22]發(fā)布以來,受到很多關(guān)注。作為熱愛技術(shù)的碼農(nóng),我們感謝大家的關(guān)心和支持,歡迎一切基于技術(shù)出發(fā)點的討論。“Show you the code”之后,我們在這里談?wù)凱hxSQL的設(shè)計和實現(xiàn)哲學(xué),也同時回答大家提出的一些疑問。
1. PhxSQL是什么?
PhxSQL是一個通過Paxos保證強一致和高可用的的MySQL集群。PhxSQL建立在Paxos的一致性和MySQL的binlog流水基礎(chǔ)上。主要原理簡單來說:
- Paxos選出主機
- 主機把本機MySQL設(shè)置成可寫的MySQL主機,在MySQL寫binlog流程中攔截binlog流水、發(fā)送到Paxos,形成全局的binlog流水
- 備機把本機的MySQL設(shè)置成只讀的MySQL備機,MySQL備機從全局binlog中拉取流水,重放和執(zhí)行,從而主備MySQL一致
- 針對常見的業(yè)務(wù)場景,PhxSQL提供兩個服務(wù)端口:強一致讀寫端口(ReadWritePort)和只讀端口(ReadonlyPort);對數(shù)據(jù)要求強一致的業(yè)務(wù),通過ReadWritePort來讀寫;只要求能讀取但不要求最新數(shù)據(jù)的讀請求(比如一些定時對賬業(yè)務(wù)),可以通過ReadonlyPort來讀取
只要有多于一半機器工作和互聯(lián),PhxSQL就可以正常工作。
2. PhxSQL的”強一致“和”高可用“是什么級別?
很多MySQL集群方案都宣稱強一致和高可用。PhxSQL這方面有什么不同?
大家熟知的Zookeeper提供強一致和高可用。一致性有很多級別,從強到弱分別是:Strict(嚴(yán)格一致性),Linearizable(線性一致性)[1],Sequential(序列一致性)[2],Causal(因果一致性),Eventual等。具體定義請參見參考文獻(xiàn)。嚴(yán)格一致性只是一個理論模型。根據(jù)相對論,由于信息傳播的速度不可能高于光速,嚴(yán)格一致性在實際中幾乎無法實現(xiàn)。線性一致性的理論定義很復(fù)雜,不太嚴(yán)謹(jǐn)直觀地講,就是任何一個客戶端都可以讀到別的客戶端寫入的最新內(nèi)容。這也是大家通常理解的強一致。
Zookeeper的強一致指“線性一致性”[3]。高可用是說只要多于一半機器工作和互聯(lián)即可在保證線性一致性的質(zhì)量下正常工作。PhxSQL的強一致是指“線性一致性”,高可用是指只要多于一半機器工作和互聯(lián)即可在保證線性一致性質(zhì)量下工作。即,
PhxSQL提供和Zookeeper相同的強一致性和高可用性!
PhxSQL提供和Zookeeper相同的強一致性和高可用性!
PhxSQL提供和Zookeeper相同的強一致性和高可用性!
重要事情說三遍:)。大家可以把PhxSQL當(dāng)Zookeeper使用,例如用來選主!
在數(shù)據(jù)庫事務(wù)隔離方面,PhxSQL支持最高級別的serializable。在性能方面,PhxSQL提供明顯優(yōu)于MySQL半同步的寫性能和幾乎相同的讀性能[22]。
細(xì)心的讀者可能注意到,和通常的“高可用、強一致”說法順序相反,這個小節(jié)的標(biāo)題中,“強一致”有意放在了“高可用”的前面。在這里需要澄清一個誤區(qū)。當(dāng)談到高可用時,有時會有意無意忽略或者降低一致性。嚴(yán)格意義上來講,可用性和一致性必須一塊討論,滿足一致性要求前提下的可用性才有意義。打個不嚴(yán)謹(jǐn)?shù)谋确?,一致性就像汽車?ldquo;安全性”,可用性就是汽車的“可用性”。如果一輛車號稱“可用性”很好,可以連續(xù)工作10年,但從來不提“安全性”,那么這輛車的品質(zhì)是值得懷疑的。
3. 為什么要做PhxSQL?
雖然有很多NoSQL、NewSQL系統(tǒng)、以及多主多寫MySQL集群、支持分庫分表的MySQL集群,MySQL傳統(tǒng)主備同步方案仍然具備很多新系統(tǒng)難以企及的優(yōu)點。MySQL主備在主機上支持完整SQL、全局事務(wù)、以repeatable read和serializable級別的事務(wù)隔離,在金融、帳號等關(guān)鍵業(yè)務(wù)中有巨大的價值。同時,現(xiàn)有復(fù)雜MySQL應(yīng)用遷移到新的非兼容系統(tǒng)成本也很高。
但是MySQL傳統(tǒng)主備方案也有其缺點。最明顯的就是主機故障后的自動換主和新舊主數(shù)據(jù)一致性(以及衍生的換主后主備一致性、各個備機之間一致性,這里就不詳加討論了),即所謂的一致性和可用性。
為了解決這個問題,有傳統(tǒng)流派:用Zookeeper、etcd、或者其它第三方來檢測心跳、選主、和切換。改造MySQL client讓其能感知新的主機?;蛘邽榱四茏寕鹘y(tǒng)MySQL client不加修改就能感知新的主機,部署MySQL代理服務(wù)器,讓其將連到自身的MySQL client請求透明轉(zhuǎn)發(fā)給新的主機。為了減少主備間數(shù)據(jù)的落后,從而降低舊主機故障、某臺備機被提升成新主機時,新舊主機之間、新主機和其它備機之間的差異,很多方案在主備同步機制上做了很多有益的工作。例如semi-sync等待多數(shù)派備機應(yīng)答,通過優(yōu)化線程和網(wǎng)絡(luò)、主備多通道、備機并行執(zhí)行binlog流水等盡量減少主備之間差異。如果主備間任何時刻都完全一致,那么任何時刻換主都是強一致的。這句話的另外一個意思是,如果無法保證主備間任何時刻完全一致,那么當(dāng)有持續(xù)不斷的更新時,任何時刻的換主都是無法保證強一致的。
傳統(tǒng)流派另外一個分支就是將MySQL本地磁盤換成更高可靠性的SAN,當(dāng)MySQL主機故障時,將SAN掛接到備機上提供服務(wù)。而當(dāng)SAN故障時怎么辦?當(dāng)需要跨機房部署時怎么辦?
意識到傳統(tǒng)流派的局限性,新出了Galera和MySQL Group Replication等。除了宣稱提供強一致性和高可用性外,還支持master-master多點寫入等誘人新特性。
世界上目前已知的經(jīng)過理論證明和實際檢驗的一致性算法屈指可數(shù):兩階段提交two-phase commit (2PC)、Paxos[4]、Raft[5]、Zookeeper Atomic Broadcast (ZAB)[3]、Viewstamped Replication[5]等。2PC雖然可以保證一致性,但在主機故障時無法工作,存在可用性問題。目前被工業(yè)界廣泛認(rèn)可和應(yīng)用的是Paxos和Raft,2PC一般在前兩者幫助選主下使用。這里的一個小建議就是:如果一個分布式系統(tǒng)宣稱支持線性一致性級別的強一致和高可用,請先檢查它使用的一致性算法。如果是新算法,請檢查它的形式化證明或者邏輯證明。
因此,為支持線性一致性和高可用,同時完全兼容MySQL,我們在MySQL的基礎(chǔ)上應(yīng)用Paxos,設(shè)計和開發(fā)了PhxSQL。
4. PhxSQL的設(shè)計原則是什么?
從實際需求出發(fā),除了前述強一致、高可用、完全兼容MySQL這3個明顯、必須的設(shè)計原則,我們還提出以下3原則。
4.1. 簡單可邏輯證明的一致性模型
這可能是明顯區(qū)別PhxSQL和其它方案的一個特點。一個經(jīng)過邏輯證明的模型才是可靠的,一個建立在可靠模型基礎(chǔ)上的系統(tǒng)也才是可信賴的。PhxSQL一致性模型建立在兩個前提上:
- Paxos保證一致性。這個大家都可以接受。
- 各個MySQL的binlog流水一致,則各個MySQL機器之間數(shù)據(jù)“一致”。這個假設(shè)小有爭議。例如即使binlog流水一致,由于不同的binlog格式、備機重放流水的不同配置,也會導(dǎo)致主備之間、不同備機之間的數(shù)據(jù)產(chǎn)生不同級別的“不同”,例如一條和當(dāng)前時間相關(guān)的insert操作。這種不一致有些應(yīng)用可以容忍,有些則不能。在這里,PhxSQL保證流水一致,而把格式和配置的自由留給應(yīng)用和DBA根據(jù)具體場景確定。在最嚴(yán)格的配置下,binlog一致,則數(shù)據(jù)一致。
在這兩個前提下,PhxSQL的一致性模型通過Paxos,使得主機寫入Paxos的binlog流水與備機從Paxos里拉取的binlog流水一致,從而保證MySQL數(shù)據(jù)的一致性。詳細(xì)證明過程我們將另外提供。模型和證明過程都很簡單,大家在讀完源碼后也可以嘗試:)。
但即使模型正確,PhxSQL正確實現(xiàn)了這個模型嗎?用通俗的話講,沒有bug。碼農(nóng)都知道這是個巨大的挑戰(zhàn)。從一個算法和模型到正確的實現(xiàn)之間差距是巨大的。例如,正確實現(xiàn)Paxos挑戰(zhàn)就很大[7]。為了盡量減少bug,我們選擇了簡單和易理解同時應(yīng)用廣泛的Paxos作為一致性協(xié)議。為了實現(xiàn)一個“生產(chǎn)”級別的Paxos,三位主要碼農(nóng)各自獨立實現(xiàn)了Paxos,在各自測試完正確性后,三套Paxos之間作為一組Paxos的獨立節(jié)點互操作檢驗正確性,最后再集體實現(xiàn)一個發(fā)行版本PhxPaxos[21]!除了單元測試、系統(tǒng)測試外,測試環(huán)境還隨機高頻率獨立重啟機器、對網(wǎng)絡(luò)包進(jìn)行亂序、延遲、重復(fù)等以對系統(tǒng)進(jìn)行充分測試。
有讀者可能問,Paxos慢且網(wǎng)絡(luò)延遲大,PhxSQL為什么不實現(xiàn)一個“優(yōu)化”版?Paxos有各種版本,例如Fast Paxos[19]、EPaxos[20]。但這些都是Paxos,遵循Paxos的基本操作,所作的改變,都做了嚴(yán)格的形式化或者邏輯證明。PhxPaxos嚴(yán)格遵照Paxos算法[4]實現(xiàn),沒有做任何改變或者“優(yōu)化”。PhxPaxos的正常寫操作的網(wǎng)絡(luò)延遲是一個網(wǎng)絡(luò)RTT,已經(jīng)是任何算法理論上能達(dá)到的最快速度。
現(xiàn)在,對于PhxSQL來說,切換主機是一件很平常和容易的操作,就像往MySQL里插入一條數(shù)據(jù)一樣平常和容易。
4.2. 最小侵入MySQL原則
MySQL是個巨大的快速演進(jìn)的生態(tài)系統(tǒng),保證PhxSQL中的MySQL與官方MySQL的兼容性與可升級性是必須的。這使得MySQL應(yīng)用可以快速遷移到和運行在PhxSQL上。這也使得PhxSQL可以迅速響應(yīng)官方MySQL的升級,將PhxSQL中的舊版本MySQL升級到新的官方MySQL,從而獲得新的特性、性能、穩(wěn)定性、和安全性提升。
這就要求PhxSQL中的MySQL對官方MySQL改動盡量少。實際上,PhxSQL版MySQL只更改了三個小地方:修改了binlog插件接口中一個函數(shù)的參數(shù);在MySQL啟動時新增了一個插件函數(shù)用于檢查MySQL本地的binlog文件,在MySQL協(xié)議中透傳了真正的客戶端IP以兼容授權(quán)功能(如果應(yīng)用不需要可以不修改)。這幾個改動是如此小,且涉及的是幾乎穩(wěn)定不變的流程,使得PhxSQL中MySQL跟隨官方MySQL升級可以無縫完成。實際上,我們正和MySQL社區(qū)溝通,希望把這幾處修改并入官方版本,從而使得PhxSQL以后可以完全使用官方版本MySQL,也使得更多人可以方便采用PhxSQL。
(a)
(b)
圖 2:PhxSQL對MySQL的關(guān)鍵修改。(a)是MySQL。(b)是PhxSQL中的MySQL。PhxSQL修改了after_flush這個函數(shù)的參數(shù),新增了在MySQL啟動時調(diào)用的before_recovery這個函數(shù)。
也正是因為這個原因,我們沒有因為可以減少模塊個數(shù)而把PhxSQLProxy、PhxBinlogSvr放到MySQL進(jìn)程內(nèi)。為了保證和應(yīng)用最廣泛的MySQL單機版兼容,也沒有在事務(wù)層和存儲層介入或修改。
4.3. 簡單的架構(gòu)、部署、和運維
在滿足要求的前提下,簡單的架構(gòu)有很多好處,例如開發(fā)、維護(hù)、診斷、維護(hù)、可靠性等等都變得容易。PhxSQL只有3+1個模塊。PhxSQL中的MySQL是必須的。PhxBinlogSvr負(fù)責(zé)全局binlog存儲和同步、選主、集群成員管理等關(guān)鍵功能。在很多MySQL主備集群方案中,使用Zookeeper、etcd、心跳檢測、Agent等承擔(dān)選主的功能。PhxSQLProxy則作為傳統(tǒng)MySQL client訪問PhxSQL中MySQL服務(wù)的代理,使得PhxSQL的換主操作對于傳統(tǒng)MySQL client透明。在其它集群方案中,一般也是通過代理MySQL proxy、或者虛擬網(wǎng)關(guān)VIP,向傳統(tǒng)MySQL client屏蔽集群的換主操作,提供透明訪問??蛇x模塊是PhxSQL client lib,它修改了MySQL client庫中的連接初始化函數(shù),允許傳入一個集群的PhxSQLProxy列表,從而在一個PhxSQLProxy沒有響應(yīng)時、自動訪問其它可用PhxSQLProxy,進(jìn)一步提高可用性。開發(fā)人員可以直接鏈接PhxSQL client lib。
PhxSQL的部署和運維都很簡單。在部署時,只要在目標(biāo)機各自裝好PhxSQL,在配置中指定集群的機器的IP列表,PhxSQL即可運行。換主這個操作已經(jīng)從運維層面轉(zhuǎn)移到PhxSQL正常的工作流程。通常MySQL集群中換主后可能需要人肉檢查數(shù)據(jù)一致性、人肉“閃回”(這可能違反一致性保證,導(dǎo)致“幻讀”);無法”閃回“導(dǎo)致必須清除某臺MySQL,重新拉取數(shù)據(jù)備份,和追流水等。這些耗時、繁重、易錯的操作在PhxSQL中已經(jīng)完全不需要。
至于熱升級和熱變更集群成員更簡單。PhxSQL支持rolling-update,可以逐步升級每臺機器。變更集群成員是指往集群添加機器、撤出機器、和替換機器(原子操作)。在保證強一致和高可用前提下,熱變更(不停服變更)是非常困難的。PhxSQL通過在Paxos中實現(xiàn)成員變更解決了這個難題。PhxSQL提供了一個變更操作命令。當(dāng)在新機器安裝和啟動PhxSQL后(要求MySQL已經(jīng)加載一份較新的數(shù)據(jù)備份),可以在現(xiàn)有集群中一鍵將新機器引入、剔除一臺舊機器、或者同時做兩者。這三種操作都是原子操作!新機器會自動追流水。想想看,相當(dāng)于Zookeeper支持熱變更成員,是不是很令人激動的特性?
PhxSQL由于在同步層使用Paxos,天然支持多數(shù)據(jù)中心、多機房部署,兩地三中心這種部署更是不在話下。對于PhxSQL來說,多數(shù)據(jù)中心和多機房部署與機房內(nèi)部署沒有區(qū)別。PhxSQL的性能取決于多數(shù)派機器之間的網(wǎng)絡(luò)延遲。
5. 為什么要開源?
作為熱愛技術(shù)的碼農(nóng),我們相信開源的技術(shù)可以使得這個世界更美好。從我們?nèi)粘i_發(fā)使用的Emacs/Vim、GCC、GDB,間接為大眾提供社交、電子商務(wù)、信息服務(wù)的Linux、Apache、MySQL、PHP,到大眾每日使用來溝通和娛樂的Android等,開源是整個互聯(lián)網(wǎng)的基石,為全世界提供許多關(guān)鍵不可或缺的基礎(chǔ)服務(wù)。我們充分享受了開源帶來的技術(shù)進(jìn)步、經(jīng)濟發(fā)展、和社會前進(jìn),我們也希望開源的PhxSQL可以回饋社區(qū),幫助更多有需要的人。
另外,我們希望通過開源更好地改進(jìn)PhxSQL。我們歡迎技術(shù)性討論和志愿者提交修改。我們承諾開源的PhxSQL會一直更新。除了一些和內(nèi)部運維支撐系統(tǒng)進(jìn)行集成的功能(PhxSQL把這些功能抽象成插件,我們針對內(nèi)部運維支撐系統(tǒng)實現(xiàn)了這些插件),開源版和內(nèi)部版本將保持一致。
6. PhxSQL的局限性
在一個不完美的世界里,完美是不存在的。我們很坦誠指出PhxSQL存在的兩個局限:
6.1. MySQL主機在執(zhí)行SQL DDL命令(例如建庫和建表命令)時可能存在一致性風(fēng)險。
由于MySQL的innodb引擎不支持DDL回滾,如果主機在innodb已經(jīng)commit這條DDL命令,但是這條命令的binlog還沒到達(dá)PhxSQL的攔截點前宕機,則這條DDL binlog會在全局binlog中缺失,從而備機也不會收到這條binlog。而為了保證線性一致性、serializable級別事務(wù)隔離、及“最小侵入MySQL”原則,我們也不想修改MySQL源碼,提前截獲DDL命令??紤]到DDL命令頻度較低,我們后續(xù)準(zhǔn)備在PhxSQLProxy加入檢查和后續(xù)審計告警。也歡迎大家提出更好方案。
6.2. 在寫入請求量很大的系統(tǒng)中,MySQL備機流水可能落后較多;如果這個時候主機死機,備機暫時無法提升成新主機,造成系統(tǒng)在一段時間內(nèi)不可寫。
為了保證線性一致性,對于要求讀取最新數(shù)據(jù)的請求(通過ReadWritePort發(fā)起的讀請求)也將失敗;需要等至少一臺備機追完流水,被提升為主機才能響應(yīng)讀取最新數(shù)據(jù)的請求。對于不需要讀取最新數(shù)據(jù)的請求(通過ReadonlyPort發(fā)起的請求),可以從任意備機執(zhí)行,但不保證線性一致性。(注意:PhxSQL保證無論MySQL主機流水領(lǐng)先MySQL備機多少,MySQL主機binlog流水和全局binlog流水是一致的,不會導(dǎo)致數(shù)據(jù)丟失和破壞線性一致性。)
MySQL備機追流水落后是基于binlog復(fù)制這種模式的一個潛在問題。事實上,不僅MySQL主備,任何一個多副本系統(tǒng),只要每個寫操作不等待所有副本返回,都會出現(xiàn)類似的有些副本落后的問題;而那些等待所有副本返回的模式,在耗時和可用性方面又存在問題??上驳氖荕ySQL 5.7版本實現(xiàn)了并行復(fù)制機制,顯著地提高了備機追流水的性能。PhxSQL將很快支持MySQL 5.7,對于寫入請求量很大的場景也可以很大程度上避免備機追流水落后的情況。
談完why,敬請期待《PhxSQL設(shè)計和實現(xiàn)哲學(xué)》的下章:why not,即我們?yōu)槭裁床恢С秩舾商匦?,例如多主多寫和分庫分表,及與Galera和MySQL Group Replication的比較等。