胡宏娟
中國(guó)電信股份有限公司江蘇分公司
(1)異地多活,是指在多個(gè)不同物理地域之間同時(shí)提供業(yè)務(wù)流量和商業(yè)服務(wù),各個(gè)地域之間不是主備關(guān)系,各個(gè)地域有自己獨(dú)立的基礎(chǔ)設(shè)施以及數(shù)據(jù)中心,業(yè)務(wù)流量可以不均等地分配在某幾個(gè)地域之間。與異地冷備相比,其不僅具有更高的成本優(yōu)勢(shì),而且更具有可擴(kuò)展性。異地多活實(shí)際上就是讓各個(gè)地域同時(shí)承擔(dān)部分業(yè)務(wù)流量,緩解單一地區(qū)的業(yè)務(wù)壓力。與冷備相比,異地多活提供了更快速的業(yè)務(wù)恢復(fù)能力,業(yè)務(wù)流量可以隨時(shí)在不同地域相互切換,達(dá)到了提供商業(yè)可持續(xù)性的同時(shí)進(jìn)行流量動(dòng)態(tài)分配的目的。
(2)CAP理論。異地多活涉及分布式系統(tǒng)和數(shù)據(jù)庫(kù),就涉及著名的一致性、可用性、分區(qū)容錯(cuò)性(Consistency-Availability-Partition tolerance,CAP)理論。一般CA是無(wú)法兼得的,P是無(wú)法放棄的,所以所有分布式系統(tǒng)的設(shè)計(jì)要么是保AP、要么保是CP,如圖1所示。

圖1 CAP理論說(shuō)明
多數(shù)互聯(lián)網(wǎng)公司都經(jīng)歷了從單機(jī)房、同城雙活、異地災(zāi)備,再到異地多活、單元化的過渡。本文著重從架構(gòu)和存儲(chǔ)兩個(gè)方面來(lái)講述。
目前的應(yīng)用一般都部署在公有云平臺(tái)上,硬件方面(包括網(wǎng)絡(luò)、電力、散熱)都是冗余的,服務(wù)方面則是通過集群模式來(lái)減少機(jī)器、機(jī)架故障造成的影響,對(duì)于SLA要求不高的應(yīng)用,基本能滿足要求。
單機(jī)房部署在整體組網(wǎng)架構(gòu)中存在單點(diǎn)隱患,一旦機(jī)房出現(xiàn)故障,服務(wù)只能暫停并等待恢復(fù),災(zāi)害性事故導(dǎo)致服務(wù)長(zhǎng)時(shí)間中斷所引發(fā)的資產(chǎn)損失以及社會(huì)效益損失,都是企業(yè)無(wú)法承擔(dān)的。所以,BAT等大型互聯(lián)網(wǎng)公司都會(huì)考慮服務(wù)的冗余,如“同城雙活”或“同城災(zāi)備”。
同城雙活和同城災(zāi)備的差別在于兩個(gè)機(jī)房是否都跑流量。(1)部署方面:當(dāng)單機(jī)房因斷網(wǎng)、斷電等故障而停止服務(wù),同城機(jī)房可及時(shí)彌補(bǔ);而同城機(jī)房的物理距離足夠近,機(jī)房之間通過光纖專線連接,跨機(jī)房調(diào)度的時(shí)延(<3 ms)一般可以忽略不計(jì),在部署方面和單機(jī)房相比并未提高復(fù)雜性。(2)分層方面:第一,接入層。CDN、4層代理、7層代理、網(wǎng)關(guān),可以選擇路由的方式,如隨機(jī)、輪詢、權(quán)重、一致性哈希等,也可以選擇有目的性的路由策略,如主機(jī)房?jī)?yōu)先、同機(jī)房?jī)?yōu)先、自定義路由規(guī)則等。第二,服務(wù)層。設(shè)計(jì)為無(wú)狀態(tài)的,應(yīng)用之間用SOA框架連通,如Dubbo、Spring Cloud、Istio等,內(nèi)部可以橫向擴(kuò)展,具備auto scaling能力。第三,存儲(chǔ)層(以一般數(shù)據(jù)庫(kù)為例,高端存儲(chǔ)設(shè)備不作描述)。一般互聯(lián)網(wǎng)公司使用MySQL,數(shù)據(jù)拓?fù)浼軜?gòu)為Master-Slave,采用主從復(fù)制(async或semi-sync),在數(shù)據(jù)中心切換之前,僅在主數(shù)據(jù)中心進(jìn)行寫操作,同步到非主數(shù)據(jù)中心的數(shù)據(jù)庫(kù),便于主庫(kù)故障時(shí)可以手工或自動(dòng)切換(failover),減少恢復(fù)時(shí)間。這種架構(gòu)問題主要是偽雙活,當(dāng)主數(shù)據(jù)中心故障時(shí),數(shù)據(jù)的一致性無(wú)法得到保障,解決方式是使用分布式數(shù)據(jù)庫(kù),利用Paxos、Raft分布式一致性協(xié)議保證強(qiáng)一致。
(1)同城雙活+異地災(zāi)備架構(gòu):同城雙活能解決架構(gòu)上的單點(diǎn)問題,但依然無(wú)法擺脫地域性的災(zāi)難,如地震、洪水等自然災(zāi)害,跨地域部署鏡像災(zāi)備機(jī)房和服務(wù)便成為進(jìn)階考慮。但大量的跨地域調(diào)度,時(shí)延往往比較可觀,如果在網(wǎng)絡(luò)設(shè)計(jì)和帶寬方面高可用設(shè)計(jì)或資源冗余不足,網(wǎng)絡(luò)抖動(dòng)往往容易影響業(yè)務(wù)的穩(wěn)定性,所以,一般異地備份機(jī)房只做災(zāi)備,采用非對(duì)等獨(dú)立部署(更小規(guī)模、各層獨(dú)立部署)不承載業(yè)務(wù)流量,存儲(chǔ)基于數(shù)據(jù)庫(kù)同步機(jī)制進(jìn)行復(fù)制。這種同城雙活+異地災(zāi)備的架構(gòu),一般稱為“兩地三中心”架構(gòu),即2個(gè)地域、3個(gè)IDC,如圖2所示。這種架構(gòu)適用于中小型應(yīng)用,由于容災(zāi)機(jī)房不跑流量,對(duì)于數(shù)據(jù)一致性要求很高的場(chǎng)景有一定風(fēng)險(xiǎn)。同時(shí),擴(kuò)容和集群伸縮性、計(jì)算、存儲(chǔ)、網(wǎng)絡(luò)等資源受限于單地域的容量。

圖2 兩地三中心架構(gòu)示意
(2)異地多活架構(gòu):地域之間不是主備關(guān)系,不同物理地域之間同時(shí)提供業(yè)務(wù)服務(wù),不涉及故障切換failover的工作(流量會(huì)有策略調(diào)度)。failover的災(zāi)備策略往往不是自動(dòng)化的,而且一般比較復(fù)雜,需要測(cè)試演練,所以維護(hù)成本、切換風(fēng)險(xiǎn)很高。異地多活架構(gòu)如圖3所示。異地多活下,各個(gè)地域機(jī)房獨(dú)立運(yùn)行,流量可以按策略調(diào)度到各個(gè)地域和可用區(qū),與異地災(zāi)備相比,其優(yōu)勢(shì)在于:(1)成本更低,因?yàn)閮蓚€(gè)機(jī)房都跑流量。(2)復(fù)雜度更高,因?yàn)樾枰婕傲髁康恼{(diào)度策略。(3)若不同機(jī)房的流量產(chǎn)生切換,則會(huì)帶來(lái)更復(fù)雜的數(shù)據(jù)同步問題。(4)伸縮性好,服務(wù)和數(shù)據(jù)可以相對(duì)平衡負(fù)載在各個(gè)區(qū)域,不受單地域資源限制。需要說(shuō)明的是,并不是所有應(yīng)用都需要做異地多活,一般對(duì)主鏈路業(yè)務(wù)、SLA要求較高、擴(kuò)容頻繁的業(yè)務(wù)優(yōu)先做,否則方案會(huì)比較復(fù)雜,工作量也比較大。

圖3 異地多活架構(gòu)示意
實(shí)現(xiàn)了異地多活,也并不代表到達(dá)了終點(diǎn)。異地多活主要的問題在于跨地域延時(shí),業(yè)界目前的解決方案主要集中在網(wǎng)絡(luò)層面(如搭建BGP網(wǎng)絡(luò)、CDN加速等)。在架構(gòu)層面,阿里2016年提出了 “單元化”概念,讓特定user的請(qǐng)求和數(shù)據(jù)收斂、封閉在同一個(gè)IDC,單元高內(nèi)聚,盡量減少跨區(qū)域訪問,即“單元封閉”。一些流量較小的長(zhǎng)尾應(yīng)用,仍然只在中心部署,配套容災(zāi)方案。
單元化的策略是按照某個(gè)業(yè)務(wù)維度拆分用戶,例如,線上商城和支付App一般是按照會(huì)員ID(userid)取模劃分單元的,物流一般是按照user所處的地域劃分。每個(gè)IDC根據(jù)URL設(shè)定路由策略調(diào)度流量,并封閉服務(wù)和存儲(chǔ),只承載本單元的請(qǐng)求。單元化架構(gòu)如圖4所示。

圖4 單元化架構(gòu)示意
對(duì)于傳統(tǒng)數(shù)據(jù)庫(kù),需要額外配套HA策略來(lái)切換Master和Slave。MySQL在本區(qū)域或者跨區(qū)域的高可用,可以通過主從復(fù)制(replication)加多副本(replica)實(shí)現(xiàn),一個(gè)實(shí)例做Master,其他實(shí)例做Standby,主故障即可切換,其他可以做本區(qū)域或者跨區(qū)域的復(fù)制,僅只讀,擴(kuò)展讀能力。數(shù)據(jù)庫(kù)集群高示意如圖5所示。

圖5 數(shù)據(jù)庫(kù)集群高可用示意
假設(shè)有5 000對(duì)MySQL主備實(shí)例,又有一套用來(lái)切換MySQL主備實(shí)例的HA工具,要做到任意一臺(tái)主庫(kù)宕機(jī),HA都能夠在極短的時(shí)間內(nèi)切換主備,需要實(shí)現(xiàn)以下功能:(1)部署監(jiān)控代理。為了監(jiān)控各個(gè)主庫(kù)的狀態(tài),需要在各臺(tái)機(jī)器上部署Agent以采集實(shí)例健康狀態(tài)。Agent以一定時(shí)間不斷向HA匯報(bào)實(shí)例的健康狀態(tài)(Heartbeat)。(2)Heartbeat與仲裁節(jié)點(diǎn)。這些實(shí)例的健康信息如果存放于一個(gè)節(jié)點(diǎn)上,也存在單點(diǎn)風(fēng)險(xiǎn)。因此,需要把風(fēng)險(xiǎn)分散到多個(gè)節(jié)點(diǎn)上,被稱為仲裁節(jié)點(diǎn)。(3)數(shù)據(jù)一致性。
到此,HA已經(jīng)是一個(gè)分布式系統(tǒng),與ZK息息相關(guān),因此,對(duì)于分布式集群的管理,最終還是要通過Paxos數(shù)據(jù)一致性協(xié)議。基于這種思路,業(yè)界涌現(xiàn)了很多分布式數(shù)據(jù)庫(kù)的解決方案。
對(duì)于傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù),無(wú)論是Oracle還是MySQL,最通用的做法是通過存儲(chǔ)共享或者日志同步來(lái)保證。
(1)Oracle的存儲(chǔ)共享。比較著名的是Oracle的真正應(yīng)用集群(Real Application Cluster,RAC),基于share everything架構(gòu),比如,3個(gè)節(jié)點(diǎn)的RAC系統(tǒng)在CPU、內(nèi)存上是做了擴(kuò)展,但是為了保證數(shù)據(jù)強(qiáng)一致性,存儲(chǔ)做了共享。這種架構(gòu)最大的弊端是各個(gè)節(jié)點(diǎn)爭(zhēng)用熱點(diǎn)塊而引起的“GC”(Global Cache)等待事件。引起“GC”事件的原因是同一時(shí)間只能有一個(gè)節(jié)點(diǎn)在更新同一個(gè)塊。Oracle RAC在Fusion Cache層做了內(nèi)存鎖,用以協(xié)調(diào)各個(gè)節(jié)點(diǎn)對(duì)同一個(gè)Block的Update操作。Block有資源Master概念,只有得到Master的授權(quán),才能對(duì)塊進(jìn)行更改。因此,在架構(gòu)設(shè)計(jì)上,Oracle引入了動(dòng)態(tài)資源管理(Dynamic Resource Management,DRM)。另外,為了解決集群的腦裂(Brain Split),Oracle引入了基于共享存儲(chǔ)的Voting Disk,來(lái)對(duì)Brain Split進(jìn)行仲裁。這種架構(gòu)顯而易見的弊端是對(duì)硬件依賴非常嚴(yán)重,任何存儲(chǔ)介質(zhì)的異常都必須進(jìn)行容災(zāi)切換,并且無(wú)法跨機(jī)房部署,做不到IDC間的容災(zāi)。而基于Paxos一致性協(xié)議的分布式數(shù)據(jù)庫(kù)系統(tǒng)很容易做到,只需將其中一個(gè)副本存放于其他IDC中即可。
(2)日志同步。對(duì)于性能的損耗較大。無(wú)論是Oracle的Data Guard還是MySQL的Binlog同步,對(duì)于Master主庫(kù)的TPS有明顯的損耗。每次事務(wù)commit,必須要等到從庫(kù)ACK并成功應(yīng)用后,主庫(kù)再返回給用戶事務(wù)提交信息。業(yè)界有非常多的優(yōu)化方案,如日志緩沖區(qū)鎖優(yōu)化、異步化、減少不必要的context switch等,但網(wǎng)絡(luò)開銷上即使采用萬(wàn)兆網(wǎng)、調(diào)優(yōu)網(wǎng)卡軟中斷模式,也很難得到改善。
以MySQL為例,如果采用異步復(fù)制(Replication),由于其異步特性,若數(shù)據(jù)未完成同步即切換,會(huì)導(dǎo)致數(shù)據(jù)丟失,PRO>0。此時(shí),被提升為新主庫(kù)的數(shù)據(jù)自然與之前的不一致,如果在同步時(shí)停止服務(wù),可用性就會(huì)受到影響,業(yè)務(wù)是無(wú)法接受的。
為改善Replication帶來(lái)的數(shù)據(jù)不一致問題,MySQL提供了半同步復(fù)制(semi-sync),即主庫(kù)需要接受從庫(kù)的ACK(代表同步到Binlog),才成功返回客戶端。對(duì)于幻讀,在MySQL 5.7版本之后,又可以通過設(shè)置AFTER_SYNC或AFTER_COMMIT模式來(lái)解決,需要注意,開啟semi-sync會(huì)影響寫性能。MySQL半同步復(fù)制AFTER_SYNC和AFTER_COMMIT兩種模式如圖6所示。

圖6 MySQL半同步復(fù)制兩種模式示意
開啟semi-sync,也不一定PRO=0,非高負(fù)載select的場(chǎng)景下,MySQL通常開啟InnoDB引擎,采用2PC進(jìn)行提交,在事務(wù)Prepare階段寫完Redo log,事務(wù)不是commit狀態(tài),需要走到Server層面,Binlog寫成功才進(jìn)行解鎖,清理Undo log的commit工作,再返回客戶端。
MySQL支持用戶自定義在commit時(shí),解決如何將log buffer中的日志刷到log files中的問題,主要方法是通過設(shè)置變量innodb_flush_log_at_trx_commit的值來(lái)控制Redo Log的刷盤策略,如圖7所示。其中,“0”:commit時(shí),寫入log buffer中,每秒寫入os buffer并同步fsync刷盤,若進(jìn)程crash,丟失1 s數(shù)據(jù)。“1”:默認(rèn)模式,commit時(shí),寫入os buffer并同步fsync刷盤,安全,但需要等待磁盤IO,性能差。“2”:commit時(shí),僅寫入os buffer,每秒fsync()將os buffer中日志寫入到log file disk中,比“0”安全,比“2”快,若宕機(jī),丟失1 s數(shù)據(jù)。

圖7 Redo Log的刷盤策略示意
通過調(diào)整參數(shù)innodb_flush_method的值來(lái)控制Redo Log的打開、刷寫模式。這個(gè)參數(shù)有4個(gè)值:(1) fdatasync:默認(rèn),調(diào)用fsync()去刷數(shù)據(jù)文件與redo log的buffer。(2)o_dsync:InnoDB會(huì)使用o_sync方式打開和刷寫redo log,使用fsync()刷寫數(shù)據(jù)文件。當(dāng)write日志時(shí),數(shù)據(jù)都write到磁盤,并且元數(shù)據(jù)也需要更新,才返回成功。(3)o_direct:InnoDB會(huì)使用o_direct打開數(shù)據(jù)文件,使用fsync()刷寫數(shù)據(jù)文件跟redo log,write操作是從MySQL InnoDB buffer里直接向磁盤上寫。(4)all_o_direct:與o_direct差別在于redo log也直接向磁盤上寫。這4個(gè)策略的選擇最終影響的是CPU、處理性能和響應(yīng)時(shí)間,如圖8所示。

圖8 innodb_flush_method參數(shù)策略示意
Sync_Binlog控制Server層面Binlog的刷盤策略。在事務(wù)prepare階段,當(dāng)MySQL進(jìn)程Crash或者系統(tǒng)宕機(jī)、Binlog是否傳輸?shù)綇膸?kù)、相應(yīng)的文件有沒有落盤,都會(huì)造成客戶端、主庫(kù)、從庫(kù)的數(shù)據(jù)不一致。MySQL的主從加上刷盤,是不能保證數(shù)據(jù)強(qiáng)一致的。如果采用semi-sync+強(qiáng)制刷盤的策略,可以做到不丟數(shù)據(jù),但是MySQL實(shí)例之間的數(shù)據(jù)一致性無(wú)法保證。比如,redo log或者Binlog任意一方丟失,那么客戶端會(huì)感知失敗,主庫(kù)恢復(fù)時(shí)會(huì)rollback,如果Binlog同步到了從庫(kù)就生效了,從而出現(xiàn)主從不一致。
為了滿足可用性要求,減少數(shù)據(jù)不一致,可以優(yōu)化策略:主庫(kù)宕機(jī)恢復(fù)后,先禁寫,避免新數(shù)據(jù)寫入主庫(kù)導(dǎo)致不一庫(kù),等把gap diff apply到從庫(kù)上,如果沒有沖突,再恢復(fù);否則,采用手工對(duì)帳訂正數(shù)據(jù)。
可用性不強(qiáng)求覆蓋100%的用戶,原則上滿足大部分用戶才是關(guān)鍵。面對(duì)跨地域的高時(shí)延和網(wǎng)絡(luò)抖動(dòng),遠(yuǎn)距離同步Binlog有很多局限性。所以,存儲(chǔ)層面的數(shù)據(jù)同步在業(yè)界會(huì)采用DRC中間件解決,比較常用的是Otter和DRC(Data Replication Center)。對(duì)比Otter,DRC更關(guān)注業(yè)務(wù)的劃分,可以實(shí)現(xiàn)單元內(nèi)封閉,而Otter只是在純數(shù)據(jù)庫(kù)層打通的技術(shù)。這類中間件的原理:從數(shù)據(jù)庫(kù)同步Binlog,異步的將數(shù)據(jù)進(jìn)行解析、過濾、標(biāo)準(zhǔn)格式化(DRC Message)、復(fù)制遠(yuǎn)程傳輸,中間件本身可以做持久化,便于實(shí)現(xiàn)一次訂閱,多點(diǎn)分發(fā)。可以在事務(wù)一致性的前提下提供秒級(jí)的DB同步,但其注重AP,犧牲了異地一致性。
在單元化異地多活的架構(gòu)里,DRC可以承擔(dān)單元和中心之間的數(shù)據(jù)復(fù)制功能,如可以采用寫中心、讀單元的模式,中間數(shù)據(jù)廣播到各單元,單元通過中心做數(shù)據(jù)同步;也可以各單元部分寫,相互同步。請(qǐng)求可以做單元封閉,單元內(nèi)的數(shù)據(jù)可以是全量的,支持區(qū)域故障時(shí)的機(jī)房切換。
比較熟悉的分布式數(shù)據(jù)庫(kù)有開源的TiDB、阿里云的RDS等,基于Paxos協(xié)議或者Paxos簡(jiǎn)化變種Raft,來(lái)實(shí)現(xiàn)分布式系統(tǒng)的數(shù)據(jù)最終一致性。Paxos有兩種實(shí)現(xiàn)思路:(1)將Paxos與實(shí)際的數(shù)據(jù)節(jié)點(diǎn)獨(dú)立出來(lái),如zk架構(gòu),比較簡(jiǎn)單,但是增加了Leader和Follower的通信,帶來(lái)了更大的時(shí)延。(2)直接將Paxos實(shí)現(xiàn)到各數(shù)據(jù)節(jié)點(diǎn)的組件中,好處是時(shí)延低,但技術(shù)難度高,比如,螞蟻的OceanBase、Google的Spanner等。
分布式數(shù)據(jù)庫(kù)對(duì)數(shù)據(jù)的每次修改都看作一次增量log,再在存儲(chǔ)上套一層Raft協(xié)議。例如,3個(gè)節(jié)點(diǎn)的集群,Raft的Leader負(fù)責(zé)寫,Leader的事務(wù)日志同步到follower,超過多數(shù)派(majority quorum)落盤后,再commit。雖然延遲明顯高,但小幅延時(shí)卻可以保證數(shù)據(jù)一致性,而吞吐可以靠集群和拆分解決。以阿里云的RDS為例,通過Raft狀態(tài)機(jī)控制事務(wù),會(huì)帶來(lái)明顯的延時(shí)問題,一般部署在同地域,依賴DRC(DTS)做異地復(fù)制。
從當(dāng)前業(yè)界數(shù)據(jù)中心級(jí)的多活架構(gòu)來(lái)看,私有云的部署一般以同城雙活或異地多活為主,更大規(guī)模的公司會(huì)嘗試采用單元化的方案來(lái)封閉數(shù)據(jù)和流量,減少異地流量的調(diào)度導(dǎo)致的網(wǎng)絡(luò)抖動(dòng),而造成業(yè)務(wù)的不穩(wěn)定。但隨著公有云和混合云方案的發(fā)展,如今的技術(shù)架構(gòu)又呈現(xiàn)了不同的發(fā)展趨勢(shì),以同城雙機(jī)房的私有云配合多云的混合云方案,將成為另一種數(shù)據(jù)中心級(jí)容災(zāi)的新趨勢(shì)。很多傳統(tǒng)企業(yè)或者較小的企業(yè)可以根據(jù)業(yè)務(wù)特性來(lái)選擇系統(tǒng)架構(gòu)方案,由于異地多活的技術(shù)門檻高、投入成本大、運(yùn)維要求高,并不適用于所有企業(yè)。就筆者所負(fù)責(zé)的系統(tǒng)而言,一般采用同城雙活,即可基本滿足業(yè)務(wù)要求。