阿里巴巴技術(shù)專家 | 高可用實踐:從淘寶到上云的差異
分享一篇文章,對于Linux入門學習者可能略顯高深,但是仔細讀的話會有巨大的收獲。這篇文章來自與阿里巴巴技術(shù)專家在Qcan大會上的演講,在這次演講中介紹了其近幾年在阿里電商平臺及阿里云上的高可用設(shè)計的經(jīng)驗,無論是入門還是進階都會很有收獲。
演講全文:

沐劍
大家好,我今天分享的題目是《高可用實踐:從淘寶到上云的差異》,取這個標題是因為會涉及到兩個方面內(nèi)容,一方面以淘寶為例子,傳統(tǒng)的IDC的時候,我們穩(wěn)定性是怎么做的,另外在云計算背景下,有很多創(chuàng)業(yè)公司是基于阿里云這樣的公有云基礎(chǔ)設(shè)施做研發(fā),在公有云的環(huán)境下怎么做好我們系統(tǒng)的高可用。
長期做穩(wěn)定性的人會有一些職業(yè)病,記得去年冬天有個周末,我要寄快遞,穿著睡衣在門口填快遞單,那時候我家里養(yǎng)了一只貓,因為怕貓跑出去,就把門關(guān)上了。寄完快遞口袋一掏發(fā)現(xiàn)自己沒帶鑰匙,冷靜了3秒鐘,打車到公司剛巧碰到同事,看我穿著睡衣來公司了問我什么情況,我說家里鑰匙忘帶被鎖在門外面了,不過沒事,我還有一把backup鑰匙放在公司。生活中很多時候都需要有一個backup。
我的花名叫沐劍,2011年加入淘寶做評價系統(tǒng),2012-2015年在店鋪平臺,負責店鋪的前臺瀏覽系統(tǒng)和后臺的RPC服務(wù),以及一些性能優(yōu)化、雙11保障的事情。到了2015年開始到了TAE團隊,開始負責云端架構(gòu)及整體高可用方案,TAE的升級版EWS現(xiàn)在也在聚石塔上面幫大量ISV和創(chuàng)業(yè)公司解決運維部署、自動化監(jiān)控和性能分析等等問題。去年我是作為阿里商家事業(yè)部雙11作戰(zhàn)項目研發(fā)的PM。2017年我開始接手商家營銷團隊。在阿里五六年的經(jīng)驗,其實就做了幾件事,比如連續(xù)五年參加了雙十一的核心備戰(zhàn),然后像去IOE、異地多活,全鏈路壓測、安全混合云、容器服務(wù)等項目參與設(shè)計和實施。
首先我會從淘寶店鋪角度分享,以前在店鋪是怎么樣做雙11保障的,后面是一些公有云相關(guān)的內(nèi)容。這是一個淘寶的店鋪系統(tǒng),這套系統(tǒng)是一個非常典型的高并發(fā)的瀏覽系統(tǒng),在前幾年的雙11峰值有20萬次的Web頁面請求,平均一個頁面對應(yīng)了20次的RPC調(diào)用,這個時候?qū)τ谡麄€系統(tǒng)的集合來說每秒的QPS是400萬次,這中間就會涉及到緩存、數(shù)據(jù)庫以及其它二方的RPC調(diào)用,對于這樣的系統(tǒng)來說,在性能、穩(wěn)定性和體驗間要做一個平衡,既不能純用太多的機器死扛這個訪問量,又要保障用戶的體驗。

從請求鏈路來說,首先DNS把CDN的VIP解析出來,分布在全球不同的區(qū)域,CDN回源到接入層分別經(jīng)過4層和7層的負載均衡,近幾年會發(fā)現(xiàn)CDN這個行業(yè)早已不僅僅局限做CSS/JS等靜態(tài)資源的緩存,也承擔了一些動態(tài)加速和收斂的特性,所以我們是通過CDN做域名收斂,收斂后會把這個流量發(fā)到統(tǒng)一接入層,然后到應(yīng)用集群,后面再經(jīng)過應(yīng)用存儲、Cache這些服務(wù)。
當我們在做穩(wěn)定性的時候,會考慮性能和穩(wěn)定性之間是什么關(guān)系,很多人認為這兩者是沖突的,比如我要保障一個東西的性能非常高的時候,要犧牲掉很多別的東西,可能你要引入一個非常新的框架或者基礎(chǔ)設(shè)施來提升性能,但它的穩(wěn)定性可能是不那么成熟的,但是從容量規(guī)劃的角度看,只有這套系統(tǒng)性能足夠好,才能承擔像雙11那樣的大訪問量。

店鋪也是一套經(jīng)歷了很多年的系統(tǒng),在應(yīng)用層上的優(yōu)化基本上已經(jīng)做到極致了,我們就轉(zhuǎn)變思路,在操作系統(tǒng)層能不能做一些優(yōu)化,這里借助了一個比較好的工具perf,在操作系統(tǒng)層面告訴你系統(tǒng)調(diào)用的開銷是集中在哪里,從perf上就可以定位到有一個百分比,可以看到是比如數(shù)組分配還是GC產(chǎn)生了大量的開銷。最初我們發(fā)現(xiàn)是異常帶來的開銷,就會看為什么這個系統(tǒng)的異常會導致20%以上的CPU開銷,最后用BTrace跟了一下異常的構(gòu)造函數(shù),發(fā)現(xiàn)是我們依賴的開源的三方包里通過異常做控制流,每一次它處理結(jié)束的時候,就拋一個EOFException出來,這個就導致了非常大的開銷,我們就把開源包替換掉了。當你依賴一些底層的東西的時候,如果對原理不太了解會給你帶來一些意料之外的事情。JVM里是有一個常量池存儲字符串常量的地方,就是一個哈希表,如果說這個表的大小不足夠大,就會從哈希查詢變成鏈表查詢,性能就會特別低。
再談一個warm up的問題,當我們應(yīng)用剛剛啟動的時候,還沒有把字節(jié)碼編譯成native code,延遲非常高,用戶就得到一個有損的服務(wù)。我們現(xiàn)在在內(nèi)部的JVM做一個功能,會采集線上系統(tǒng)的調(diào)用,把熱點方法收集下來做分析,在應(yīng)用把真實流量掛上去之前,已經(jīng)預先把所有的熱點方法編譯成native code保證這個性能。開源界也有其他的方案,比如Azul的Zing有個ReadyNow,IBM的J9有個AOT,也是做類似的事情。另外這里我放了一個Github的鏈接,這個agent能夠讓你在perf界面里直接看Java Method的開銷。

談到緩存,Cache里有一些小技巧,在做雙十一備戰(zhàn)時發(fā)現(xiàn)一個店鋪的基礎(chǔ)服務(wù)平時日常就每天有100億的調(diào)用量,當時是幾十臺機器估了一下可能要成倍增長,成本是非常高的,怎么解決這個問題,當時寫了個富客戶端,讓業(yè)務(wù)方先去查我們分布式Cache,如果命中就直接返回來,如果不命中再走我們的服務(wù)端查。這種情況下,只要你能夠保證命中率足夠高,比如98%的命中率,就意味著只有2%是需要后端服務(wù)器承擔剩下的請求,用非常少的服務(wù)器去承擔非常大的流量,這是成本和性能間的權(quán)衡。
在緩存方面,我們很少會關(guān)心緩存的高可用是怎么部署的,它是一個偏運維的內(nèi)容,我把緩存的部署簡化成一個雙機房的模型,因為它在高可用里是最簡單的場景。對于緩存來說有兩種經(jīng)典部署模式,第一種叫共享集群部署,在IDC里我的應(yīng)用是分機房部署的,Cache集群也是分機房部署,對于應(yīng)用服務(wù)器來說,兩邊的Cache對他來說邏輯上是一個集群,會往IDC 1的Cache寫一半過去,往IDC 2也寫一半過去,這種部署的好處在于,機房間網(wǎng)絡(luò)斷掉的時候,有一半的數(shù)據(jù)是在緩存的,保證一半的數(shù)據(jù)能夠命中,不會直接死掉,另外對成本上相對比較友好,沒有浪費任何一個Cache的節(jié)點,這個Cache本身是復用的。但是也正如剛才說的問題,如果中間斷掉了,有一半的命中率是有損的,所以就誕生了另外的一個部署模式,就是獨立部署,不管你哪個機房掛掉,命中率是基本不變的,兩邊同時保持了98%的命中率,但是它是成本不友好的,兩邊要同時部署,同時承擔副本的作用,并且失效時,要同時失效另外一個IDC 2,這樣才保證一致性。
在緩存上,我認為一切東西都可以被緩存的,通常我們認為緩存跟實際數(shù)據(jù)庫里存在的東西可能是不一樣的,有幾毫秒的延遲或者怎么樣,所以我們設(shè)計一個系統(tǒng)的時候,對一致性要求非常高的時候,會傾向于不用緩存,用數(shù)據(jù)庫扛這個流量。但以MySQL為例,InnoDB里有一個很重要的緩存Buffer Pool,對于一個數(shù)據(jù)庫,在冷庫的情況下用一堆SQL去查它,和慢慢預熱完再查它的時候效果是不一樣的,這個是我們當初在做異地多活時面臨的一個問題,例如我已經(jīng)有一個機房,希望建立一個新單元去承擔這個機房的流量,當我建完這個單元,把所有的應(yīng)用都部署好了后,把流量切50%過來會怎么樣,假設(shè)這兩個單元的機器數(shù)一樣,這個單元會掛,因為這個數(shù)據(jù)庫是冷的,緩存是空的,不能承擔之前那個單元數(shù)據(jù)庫所能承擔的QPS。
現(xiàn)在業(yè)界有很多叫API網(wǎng)關(guān)或者CDN,他們在邊緣節(jié)點也做了一層短暫的Cache,可能只Cache 50或者100毫秒,但是當你系統(tǒng)受到攻擊的時候可以拯救你后端的應(yīng)用系統(tǒng),攻擊引發(fā)的命中率通常比較高,有這50毫秒的緩存,可能后端只有幾百個QPS過來,那個流量你是可以承受的。
在高可用里兩個非常經(jīng)典的做法是限流和降級,在阿里雙11,有一位老兵說過一句話,他說當雙11到來的時候,任何一個系統(tǒng)都可能出問題,你要做的是對你的上游限流,對你的下游限流。怎么理解,當上流的流量超過你的能力的時候就要限流,當下游比如DBA告訴你數(shù)據(jù)庫壓力很大了,那就對下游限流,只要保證住這個限流,你基本不會掛,每個系統(tǒng)都做到這個的時候,整個系統(tǒng)都是可用的。當流量超出你掌控的時候,這個做法可以讓你成為這個暴風下的幸存者。

對限流降級的思考,第一限流降級考驗的是什么問題,我認為本質(zhì)上考驗的是故障自恢復能力,在平時工作中會遇到機房斷網(wǎng)或者停電,每半個月都會做斷網(wǎng)演練,不告訴你發(fā)生什么,就把這個網(wǎng)切斷,看你的應(yīng)用O不OK,一般是在晚上兩三點,接到很多的機房報警,這個時候看你的架構(gòu)設(shè)計的是否足夠可用,如果足夠可用就沒問題,不會造成什么影響,繼續(xù)睡覺,如果設(shè)計不好,就得爬起來立即處理。
而開關(guān)降級最大的作用,比如我們發(fā)現(xiàn)一些線上的問題,第一反映是趕緊回滾,但是當你的系統(tǒng)很大的時候,特別像Java這種,一個系統(tǒng)啟動要啟動幾分鐘,你的回滾完成,20分鐘都過去了,這個過程對用戶來說都是有損的,而開關(guān)可以在一瞬間把所有的邏輯切到老的。這個是避免回滾時間導致的問題。開關(guān)有的時候能救命,如果沒有這個開關(guān)的話,避免問題放大就只能回滾,所以開關(guān)是一個很大的價值所在。

另外一點非常重要的是,在設(shè)計一個技術(shù)方案的時候,就會把容災的設(shè)計融入到方案里。比如在設(shè)計技術(shù)方案的時候,在最后一章單獨有一個容災設(shè)計,這個節(jié)點里任何服務(wù)掛掉的時候,你要保持什么樣的方式保持這個服務(wù)是可用的。
在容災設(shè)計時有幾點必須考慮,比如我引了一個新jar包或者調(diào)了一個新的RPC的服務(wù)、引入了分布式的存儲,以前沒用過也不知道它穩(wěn)不穩(wěn)定,第一想法是它肯定會掛,它掛了我們怎么做,我們當時在做前臺系統(tǒng)的異步化的時候,因為Redis支持map的數(shù)據(jù)結(jié)構(gòu),所以我們就是用Redis的hmget從這個map里拿出部分的key減少網(wǎng)卡的流量,但即使這個掛掉了,我們還會走老的Cache,只不過網(wǎng)卡流量會大一些,但是對用戶的服務(wù)是無損的,所以這里要考慮如果它掛了怎么做降級,有什么樣的恢復流程。
另外是發(fā)布計劃,在新系統(tǒng)上線時就會關(guān)注這些問題,比如這次有沒有做數(shù)據(jù)遷移,比如以前我是8個庫不夠用了我拆到16個庫或者32個庫,中間一定是有數(shù)據(jù)遷移的,涉及到數(shù)據(jù)遷移一定要有一套對賬系統(tǒng)保證這個數(shù)據(jù)是新數(shù)據(jù)和老數(shù)據(jù)是對得平的,不然一定有問題,因為我們是做交易相關(guān)的,訂單、金額絕對不能出問題。
另外是你的發(fā)布順序是不是有依賴,如果出了問題的時候,誰要先回滾,這里是取決于技術(shù)設(shè)計。另外是否要通過客服公告的方式告訴外部用戶說有5分鐘的不可用,如果真的有用戶打電話有疑問客服同學可以向用戶解釋。
在高可用這個領(lǐng)域做久了會有一種直覺,這個直覺很重要,來源于你的經(jīng)驗轉(zhuǎn)換成這種直覺,但是對于一個成熟的團隊來說,需要把這種直覺轉(zhuǎn)化為產(chǎn)品或工具。有很多牛人他們的技能都只能叫手藝,你需要把這種手藝轉(zhuǎn)換成產(chǎn)品和工具。
2015年我去做云產(chǎn)品,這里給大家分享下我們是怎么樣幫客戶包括我們的系統(tǒng)在云上是做高可用的。

首先看兩個經(jīng)典故障案例,第一個是Gitlab生產(chǎn)數(shù)據(jù)庫刪了,它恢復了很久,Snapshot等全都沒有生效,做了五六層的備份也都沒有什么用。這個事情說明第一我們的故障要定期演練,比如中間件在做的線上故障演練,你說你的系統(tǒng)可用性好,我把這個主庫斷了,虛擬機掛掉幾臺試試,做這些演練就可以知道你這個容災體系是不是可靠的,如果沒有這個演練的話,當真正的故障發(fā)生時你才會發(fā)現(xiàn)這個東西是不OK的。
另外一個很典型的問題,Gitlab對備份的原理是不夠了解的,比如當時用的PostgreSQL的一個版本,當時是有問題的,沒有驗證,開發(fā)人員對這個又不是特別了解的情況下就會出現(xiàn)這個問題,這就是為什么要去了解你的依賴以及你依賴的依賴。去年我們做壓測,有個應(yīng)用一邊壓測一邊在優(yōu)化做發(fā)布,發(fā)現(xiàn)第一批發(fā)的起不來了,就只是改了一兩行代碼加日志,他就去看什么原因,最后發(fā)現(xiàn)依賴的某個jar包依賴一個配置,而這個配置在壓測中被降級了,一個jar包就把應(yīng)用啟動卡住了。如果在雙十一當天或者在平時業(yè)務(wù)高峰期的時候發(fā)現(xiàn)這個問題是來不及修復的。所以這個時候,我們就要求,依賴的二方j(luò)ar包必須看一下里面是怎么實現(xiàn)的,依賴哪些東西。
反過來說,別人依賴我的客戶端就意味著他不僅依賴著我的服務(wù)還依賴著我的緩存,這個緩存出了問題對他也有影響,我們每年雙十一前有一個強弱依賴梳理,不僅要梳理自己應(yīng)用里面的,還有依賴的所有東西都梳理出來,中間任何一個節(jié)點掛掉了你應(yīng)該怎么辦,需要給一個明確答復。第二個故障案例是今年發(fā)生的,AWS S3敲錯了一個命令把基礎(chǔ)核心服務(wù)下線了,有一個對象索引服務(wù)和位置服務(wù)系統(tǒng)被offline,后來也做了一些改進,每次敲的命令有一個靜默期,讓你有個反悔的機會,線上有個最小的資源保證服務(wù)。
這個給我們帶來的啟示是什么,云服務(wù)本身也是會發(fā)生故障的,比如買了云數(shù)據(jù)庫,我們沒有辦法假設(shè)它是100%可用的,當它出現(xiàn)問題我們怎么辦,是給云廠商提工單說什么時候能恢復,還是我自己能夠有一個容災的方案解決這個問題。從2015年開始,我們越來越多地發(fā)現(xiàn),對架構(gòu)可用性最大的威脅是什么?在市政施工里一定幾率就會莫名其妙搞出光纜被挖斷等故障,我們不得不考慮,當云服務(wù)本身出現(xiàn)問題我們該怎么辦。
所以我們需要有一套面向云的高可用架構(gòu)。在很早以前有廠商提出類似SDN的一個概念,叫SDI——軟件定義基礎(chǔ)設(shè)施,過去我們發(fā)現(xiàn)只有大廠可以做這個事情,設(shè)計一套很復雜的管理系統(tǒng)幫他實現(xiàn),這里放一個路由器,這邊放一臺虛擬機,可以通過軟件的控制流去控制這個東西。但是在云的時代,資源變得很容易獲得。以阿里云為例子,可以用API隨時創(chuàng)建新的虛擬機,新的負載均衡,或者是新的存儲,都可以通過API隨時創(chuàng)建隨時銷毀,這對于你的基礎(chǔ)設(shè)施的靈活度非常有好處。
以前有的人會覺得,性能問題或者容量問題應(yīng)該通過性能優(yōu)化的方式解決,通過一些黑科技方式解決,加機器會覺得很low。但我覺得一個問題如果能簡單用加機器來解決是很不容易的,意味著對你的整個架構(gòu)的水平擴展性要求非常高,而且解決效率很高,加機器就解決了,而對一些中心化的系統(tǒng)來說就比較麻煩,加機器都加不了,可能要把機器關(guān)掉升配置再重新拉起來。所以我們說,在公有云上面,在資源如此容易獲得的情況下要充分利用這個特性,要做一個能夠做水平擴展的架構(gòu)。

那么第一步要做什么,前兩年很火的容器、微服務(wù),本質(zhì)上都是解決了是無狀態(tài)的應(yīng)用怎么做自動化的擴容這個問題。右邊這個圖上面,上面是一個負載均衡,中間是一個前端的服務(wù),后端是一個無狀態(tài)的后端服務(wù),底層是MQ、對象存儲、數(shù)據(jù)庫這些東西,如果我們能夠把前端和后端的無狀態(tài)服務(wù)第一步先容器化,就可以做到當流量過來的時候,只要后端的存儲沒有問題,整套架構(gòu)就是能夠水平擴展的。
從去年公開的報道和故障來看,很多人有個誤會是說云廠商的機器應(yīng)該是不會掛的,我買了一臺云廠商的虛擬機應(yīng)該是隨時可用的,即使不可用云廠商也要幫我解決熱遷移的問題,熱遷移在業(yè)界是很復雜的問題,不光涉及到磁盤存儲的遷移,也涉及到內(nèi)存是要做遷移的,可能還要用RDMA。并且對于傳統(tǒng)的IDC來說,不管物理機還是虛擬機都是有可能掛的,對云也不例外。當我們在使用公有云服務(wù)的時候,都是會掛的,這是個心理準備。不光是機器,包括負載均衡是不是也有可能掛,下面的消息隊列或者數(shù)據(jù)庫是不是也有可能會掛,當你基于任何東西都可能會掛的前提設(shè)計一個系統(tǒng)的時候,才能真正做到這個系統(tǒng)在任何情況下都不會受底層云服務(wù)的故障影響。

而對于不同的云服務(wù)來說是有不同的容災策略。比如一臺虛擬機掛了,通常來說負載均衡不管是4層還是7層都會做健康檢查,掛了健康檢查不通自動會把流量切斷。如果我的負載均衡掛了怎么辦,如果DNS有健康檢查那就方便了,如果沒有的話可能就要設(shè)計一個旁路系統(tǒng)解決這個問題,發(fā)現(xiàn)這個已經(jīng)不通了,就自動把它從DNS上摘掉。
不管是云服務(wù)發(fā)生故障還是自己應(yīng)用發(fā)生故障有個大原則是如何最快速解決問題,就是一個字,切。為什么要做異地多活,為什么要把流量往各個地方引,切流量是解決問題最快的,把壞流量切到好的地方馬上就解決了,如果你要等定位問題解決問題再上線客戶就流失掉了。對淘寶來說也是一樣,當某一個單元低于我們認為的可用性的時候,我們會把這個單元的流量引到另外一個可用的單元,當然前提是那個單元的容量是足夠的。
彈性是不是萬能的?所有的云服務(wù)都是彈性的,彈性其實不是萬能的,容量規(guī)劃仍然是有必要的,不然就沒必要做雙十一備戰(zhàn)了。這里有一個你需要付出的代價,彈性的過程往往是需要時間的,那么容量規(guī)劃在這個環(huán)節(jié)中起到的作用就很重要,當真的逼不得已的時候,我要擴容了,怎么保證我擴完容之前系統(tǒng)不雪崩?就是要結(jié)合之前的限流,盡可能保障每個客戶得到他應(yīng)有的服務(wù),但是也要保障系統(tǒng)的穩(wěn)定性。
Region和Availability Zone這兩個,當實踐的時候,購買虛擬機、負載均衡或者數(shù)據(jù)庫,一定要選擇多可能區(qū)的服務(wù),比如阿里云買SLB是可選可用區(qū)的,有個主可用區(qū)和副可用區(qū),如果一邊掛了可以切換到另外一邊,RDS也是一樣的。

這幅圖是一套典型基于公有云的一套架構(gòu),不叫異地多活,應(yīng)該叫跨區(qū)域設(shè)計。左右兩個大框是兩個城市,左邊是北京,右邊是上海,每個里面又有不同的機房可用區(qū)去承擔這個流量,假如北京的掛掉了,就切,原來是在A可用區(qū),就切到B可用區(qū),這時候A可用區(qū)沒有流量進來,通過切換的方式能夠把這個服務(wù)快速恢復。下面畫了一個跨區(qū)域復制,我們在異地多活項目里,涉及到了跨城市跨數(shù)據(jù)中心的復制,比如我的北京是提供寫服務(wù)的,上海要提供讀服務(wù),就要通過這種方式同步數(shù)據(jù)過去。
最后,我們在招聘,招一些志同道合的小伙伴。我看到很多人做招聘,他們都在說我們想要什么樣的人,需要你具備什么樣的技能,我的想法可能不太一樣,我會先思考你到我們團隊能得到什么,我能給你什么東西。那么第一可以參與到阿里最前線的作戰(zhàn)經(jīng)驗,今年2017年雙十一大促可以跟我們一起做,第二我們團隊內(nèi)部有非常好的氛圍,可以聽到來自阿里巴巴各領(lǐng)域的專家像魯肅、畢玄等大師的分享,第三可以飛速成長,一對一師兄帶領(lǐng),快速度過適應(yīng)期。新零售新技術(shù),會創(chuàng)造出下一個時代的商業(yè)文明,對于技術(shù)人員來說我們不光有技術(shù)的思維,也要有業(yè)務(wù)和商業(yè)的思維來培養(yǎng)自己。
最后是我的郵箱,如果大家想交流可以發(fā)郵件給我:mujian.wcc@alibaba-inc.com