且看從容應(yīng)對14.3萬TPS的Twitter是怎樣煉成的!
記得剛接觸宮崎駿老爺子的作品是在高中時(shí)代,然而當(dāng)時(shí)正值青春年少外加深受灌籃高手等動畫“荼毒”,對宮老爺子的《千與千尋》、《龍貓》、《魔女宅急便》、《天空之城》之流也就完全不感冒了。再次接觸宮老爺子的動畫則是在大學(xué)期間,記得當(dāng)時(shí)熬了N個(gè)通宵把全集都過了好幾遍,不得不感慨作品只有在相應(yīng)的情景下才能釋放價(jià)值。從這個(gè)觀點(diǎn)上看,近日網(wǎng)上流傳的“宮老爺子還能再戰(zhàn)十年!”也并不為過。
NTV電視臺于2013年8月2日晚第14次重播了《天空之城》,當(dāng)劇情發(fā)展到男女主角巴魯和希達(dá)共同念出毀滅之咒“Blase”時(shí),一舉將tweet發(fā)送峰值帶到了每秒143199次,這個(gè)峰值高于2013日本新年33388次/秒,也高于拉登之死(5106次/秒)、東日本大地震(5530次/秒)、美國流行天后碧昂斯宣布懷孕(8868次/秒)。

毀滅之咒“Blase”摧毀了天空之城,同樣也摧毀了經(jīng)過充分準(zhǔn)備的2ch,然而卻鑄就了當(dāng)下Twitter的***峰值。新的峰值是平均值(5700TPS)的25倍,當(dāng)下用戶平均每天發(fā)送的tweet是500萬條左右。在峰值期間,Twitter一直保持著高可用性,對比3年前世界杯上的表現(xiàn)無疑有著天壤之別。近日Twitter在技術(shù)博客上公布了為什么會重鑄Twitter以及新系統(tǒng)打造的相關(guān)細(xì)節(jié),以下為譯文:
重鑄Twitter決心
當(dāng)2010年人們將Twitter作為世界杯體驗(yàn)實(shí)時(shí)分享工具時(shí),每一腳射門、每一次罰球、以及裁判出示的每一張紅牌或黃牌產(chǎn)生的tweet都會對系統(tǒng)造成不同程度的影響,同時(shí)也會造成程序短時(shí)間內(nèi)的不可用。2010世界杯期間,工程師日以繼夜的工作,拼命的改善系統(tǒng)性能。不幸的是,這樣的努力瞬間被Twitter的快速增長淹沒。
有過這次經(jīng)歷,我們清楚了只有重新架構(gòu)才能匹配網(wǎng)站的增長,同時(shí)讓系統(tǒng)變得更加可靠。隨后我們就開啟了漫漫的重構(gòu)之路,而當(dāng)下Twitter已經(jīng)可以從容應(yīng)對類似《天空之城》重播、超級碗以及全球新年夜慶典這樣的事件,新的架構(gòu)不僅能彈性的應(yīng)對負(fù)載,還可以幫助工程團(tuán)隊(duì)快速發(fā)布新特性,包括了跨設(shè)備的消息同步,Twitter cards可以讓服務(wù)變得更加的豐富并擁有更多的內(nèi)容,同樣還帶來了囊括用戶和故事的搜索體驗(yàn)。為了實(shí)現(xiàn)這些,我們甚至改變了工程組織,下面就來一睹服務(wù)打造細(xì)節(jié):
三年磨劍鑄輝煌
重新架構(gòu)
在2010世界杯塵埃落定后,我們審視了整個(gè)系統(tǒng),并發(fā)現(xiàn)眾多需要從根本上改善的地方:
1. Twitter曾運(yùn)行著世界上***的RoR堆棧,并將這個(gè)技術(shù)推到了一個(gè)絕對高度:那個(gè)時(shí)候,大約200個(gè)工程師在為之奮斗,它確實(shí)讓Twitter渡過了一個(gè)用戶爆發(fā)性時(shí)期,不管是新用戶的增加,還是負(fù)載增加的處理。這個(gè)系統(tǒng)一度支撐了所有運(yùn)作,從管理數(shù)據(jù)庫、memcache連接到網(wǎng)站的渲染及公共API的呈現(xiàn),這些全部包含在一個(gè)代碼庫里。它不僅讓工程師的整合變的困難,同樣也困擾了工程團(tuán)隊(duì)的管理及并行作業(yè)。
2. 存儲系統(tǒng)的吞吐量達(dá)到了極限——之前Twitter使用的是純粹的MySQL存儲系統(tǒng),進(jìn)行分片,同時(shí)系統(tǒng)只有一個(gè)主節(jié)點(diǎn)。然而從系統(tǒng)展示的速度上來看,tweet的吞吐率確實(shí)出現(xiàn)了問題。這樣我們就必須重建一個(gè)新的數(shù)據(jù)庫以應(yīng)對增長率,因?yàn)闊衢T數(shù)據(jù)的讀取已越來越頻繁。
3. 取代從工程方面徹底的解決問題,我們曾嘗試過投入更多主機(jī);然而隨后就發(fā)現(xiàn),前端Rbuy主機(jī)并不能處理預(yù)期(根據(jù)性能)的事務(wù)量——從以往的經(jīng)驗(yàn)看,這些主機(jī)完全可以勝任更多的事務(wù)。
4. ***,從軟件角度上看,我們發(fā)現(xiàn)我們一直在鉆優(yōu)化這個(gè)牛角尖,我們開始在性能和效率與代碼庫的可讀性和靈活性之間做權(quán)衡。
綜上所述,系統(tǒng)的重新架構(gòu)已迫在眉睫,我們給自己定下了3個(gè)目標(biāo)和挑戰(zhàn):
1. 我們希望打造出兼具性能、效率及靈活性的大型基礎(chǔ)設(shè)施——我們希望能改善Twitter上的平均延時(shí),同樣我們希望通過離群值給Twitter帶來統(tǒng)一的體驗(yàn)。我們希望將賴以運(yùn)行的服務(wù)器降到十分之一。我們希望對故障進(jìn)行隔離以避免大規(guī)模宕機(jī)——隨著服務(wù)器規(guī)模的日益增大,這點(diǎn)就尤為重要,因?yàn)榧旱淖兇笠馕吨鳈C(jī)故障將愈來愈頻繁。故障是不可避免的,所以我們希望它們發(fā)生在一個(gè)更容易控制的模式下。
2. 我們希望通過將相關(guān)的代碼放到一起,以獲得更清晰的邊緣——我們已經(jīng)感受了整片代碼所帶來的困擾,所以我們打造一個(gè)松耦合、基于服務(wù)的架構(gòu)。我們目標(biāo)是封裝和模塊化的***實(shí)踐,當(dāng)然是在系統(tǒng)級,而不是類、模塊或者是包一級。
3. 最重要的是,我們希望能更快的發(fā)布特性。我們希望團(tuán)隊(duì)能并行的運(yùn)作,每一個(gè)小組都可以在不依賴其它小組的情況下獨(dú)立的完成局部決策,并將特性發(fā)布給用戶。
我們POC了新的架構(gòu),雖然不是所有的努力最終都能符合上述目標(biāo),但是最起碼做到了一部分并且解決了工具問題,當(dāng)然,還得到了比現(xiàn)下更令人滿意及可靠的基礎(chǔ)設(shè)施。#p#
JVM與Ruby Vm
首先我們從CPU、RAM及網(wǎng)絡(luò)三個(gè)角度評估了前端服務(wù)器層,我們發(fā)現(xiàn)基于Ruby的架構(gòu)明顯受限于CPU和內(nèi)存,然而單機(jī)器的請求并沒有達(dá)到,網(wǎng)絡(luò)帶寬同樣也沒有接近飽和。在那個(gè)時(shí)候,我們的Rails服務(wù)器只能服務(wù)單線程,同樣,每次也只能處理一個(gè)請求。每個(gè)Rails主機(jī)都運(yùn)行了一定數(shù)量的Unicom進(jìn)程,用以提供主機(jī)級的并發(fā)性,但是重疊率浪費(fèi)了太多的資源。歸結(jié)起來,Rails服務(wù)器每臺主機(jī)每秒只能處理200到300個(gè)請求。
然而Twitter使用率的增長卻并未放緩,這樣一來我們就必須投入海量的主機(jī)才能匹配用戶的增長曲線。
在那段時(shí)間,Twitter已經(jīng)擁有部屬超大規(guī)?;贘VM服務(wù)的經(jīng)驗(yàn)——Twitter的搜索引擎就是使用Java編寫,而Streaming API基礎(chǔ)設(shè)施以及社交圖系統(tǒng)Flock都是使用Scala編寫,同時(shí)我們也迷戀JVM帶來的性能,如果選用Ruby VM的話,那么性能、靈活性以及效率都很難符合我們的目標(biāo),所以我們開始編寫可以運(yùn)行在JVM上的代碼。我們預(yù)計(jì)重寫代碼庫可以讓我們獲得10倍的性能提升,而今天我們每臺服務(wù)器每秒已經(jīng)可以處理1到2萬的請求。
對JVM的信任還基于公司中有很多工程師有著豐富的JVM經(jīng)驗(yàn),我們有信心將Twitter帶入JVM后可以得到本質(zhì)上的提升。這樣下面需要做的就是拆分架構(gòu),并弄清楚不同服務(wù)之間的交互方式。
程序設(shè)計(jì)模型
在Twitter的Ruby系統(tǒng)中,并發(fā)控制被放在進(jìn)程級——單網(wǎng)絡(luò)請求通過進(jìn)程排隊(duì)。這個(gè)進(jìn)程會一直占用資源,直到請求結(jié)束。這樣將同時(shí)增加復(fù)雜性及架構(gòu)難度,Twitter需要完成基于服務(wù)的轉(zhuǎn)變。同時(shí)因?yàn)镽uby進(jìn)程使用單線程模式,后端系統(tǒng)延時(shí)將直接影響到響應(yīng)時(shí)間。在Ruby中有很多選擇來增加并發(fā)性,然而JVM中卻并沒有一個(gè)標(biāo)準(zhǔn)選擇。JVM擁有著支持并發(fā)的設(shè)計(jì)和基元,讓我們可以建立一個(gè)真正的并發(fā)程序設(shè)計(jì)平臺。
這樣,就需要一個(gè)獨(dú)立且統(tǒng)一的方式去思考并發(fā)性,特別是網(wǎng)絡(luò)的思考。眾所周知,編寫并發(fā)代碼(以及并發(fā)的網(wǎng)絡(luò)代碼)是非常困難的,并且需要使用多種方式。事實(shí)上,這方面我們早就有所體會。然而在系統(tǒng)被分解成不同服務(wù)的初期,團(tuán)隊(duì)并未使用統(tǒng)一的途徑;比如從客戶端到服務(wù)的failure semantics就不能很好交互:我們并沒有一致的back-pressure機(jī)制讓服務(wù)器給客戶端返回信號,因此出現(xiàn)了客戶端瘋狂重試服務(wù)引起的驚群效應(yīng)。這個(gè)經(jīng)歷讓我們明白統(tǒng)一、互補(bǔ)的客戶端及服務(wù)器庫的重要性,它可以在多個(gè)方面起到作用:連接池、故障轉(zhuǎn)移策略以及負(fù)載平衡,將我們連成一體,我們把Futures和Finagle拉到了一起。
現(xiàn)在不僅有了統(tǒng)一的方式,同樣合并了系統(tǒng)所需核心庫;如此我們就可以取得更快的進(jìn)展,而不用過多關(guān)注每個(gè)系統(tǒng)的操作方式,從而把精力放到應(yīng)用程序和服務(wù)接口上。
獨(dú)立系統(tǒng)
Twitter的***改變就是從一個(gè)整體的Ruby應(yīng)用程序轉(zhuǎn)變?yōu)榛诜?wù)的架構(gòu),我們首先聚焦建立tweet、timeline及用戶服務(wù)——這也是Twitter的“核心名詞”。這種轉(zhuǎn)變讓我們可以更清晰的抽象出邊緣、團(tuán)隊(duì)級所有權(quán)和獨(dú)立性。在Twitter王國,不僅需要理解整個(gè)代碼庫的專家,還有需要模塊以及類級別的所有者。越來越大的代碼庫讓全局理解變得不可能,然而在實(shí)際工作中,只有模塊和類級別的所有者根本不能解決問題。我們的代碼庫變得很難被維護(hù),而團(tuán)隊(duì)不停的去挖掘歷史代碼以便清楚某一功能。大規(guī)模錯誤發(fā)生時(shí),搞清楚狀況就像組織去遠(yuǎn)海狩獵鯨魚;到了***,我們在糾錯上花費(fèi)的時(shí)間要遠(yuǎn)大于新特性的發(fā)布,這是任何人都不愿看到的情況。
我們追求的永遠(yuǎn)都是允許并行設(shè)計(jì)的面向服務(wù)架構(gòu),我們贊同網(wǎng)絡(luò)RPC接口,然后獨(dú)立的開發(fā)系統(tǒng)內(nèi)部構(gòu)件,然而這同樣意味著每個(gè)服務(wù)的邏輯都必須在內(nèi)部實(shí)現(xiàn)。如果我們需要對tweet相關(guān)部分做改變,那么只需要在一個(gè)位置做出更改(Tweet 服務(wù)),這種變化就可以貫穿整個(gè)操作系統(tǒng)。然而在實(shí)際操作中,發(fā)現(xiàn)并不能完全實(shí)現(xiàn)這個(gè)目標(biāo),比如:一個(gè)Tweet服務(wù)中的改變可能會需要其他服務(wù)做出相應(yīng)的改變。雖然沒有做到完全獨(dú)立,但是大部分的情況下還是可以的。

這個(gè)系統(tǒng)架構(gòu)就是我們一直期待的,每個(gè)團(tuán)隊(duì)都可以獨(dú)立和快速的運(yùn)轉(zhuǎn)。這就意味著團(tuán)隊(duì)可以調(diào)用后端系統(tǒng)對所屬服務(wù)進(jìn)行操作,非常有益大型應(yīng)用程序運(yùn)營。#p#
儲存
即使應(yīng)用程序被切分成服務(wù),存儲上仍然存在非常大的瓶頸。這時(shí)的Twitter使用的還是一個(gè)主MySQL數(shù)據(jù)庫,為此我們采取了臨時(shí)策略——將每條tweet都儲存為數(shù)據(jù)庫中的一行并進(jìn)行順序存儲,當(dāng)數(shù)據(jù)庫容量耗盡時(shí)會建立一個(gè)新的數(shù)據(jù)庫,并在應(yīng)用中進(jìn)行配置。這個(gè)策略為我們爭取到了一定的時(shí)間,但是峰值時(shí)的tweet吞吐量瓶頸依然存在,因?yàn)樗麄兛偸潜豁樞虻拇鎯Φ揭粋€(gè)單獨(dú)的數(shù)據(jù)庫主節(jié)點(diǎn)中,所以讀負(fù)載只被分擔(dān)到一小部分的數(shù)據(jù)庫主機(jī)中。因此,我們需要為Tweet的存儲選擇一個(gè)不同的分片策略。

我們開發(fā)了Gizzard,幫助我們建立分片及容錯的分布式數(shù)據(jù)庫,用它來服務(wù)tweet。我們還建立了T-Bird。這種情況下,Gizzard需要處理大量的MySQL數(shù)據(jù)庫——當(dāng)tweet被傳入系統(tǒng)時(shí),Gizzard會首先得到它,然后選擇一個(gè)合適的數(shù)據(jù)庫。當(dāng)然,這就意味喪失了MySQL的唯一ID生成能力。Snowflake被開發(fā)用以解決這個(gè)問題,它可以幫助建立tweet的ID。一旦有了標(biāo)識符,就可以依靠Gizzard進(jìn)行存儲。如果哈希算法非常有效,并且tweet接近均勻分布,那么就可以通過增加目標(biāo)數(shù)據(jù)庫的數(shù)量來提高吞吐量。讀取同樣均勻的分布在整個(gè)集群上,而不是被綁在“最近”的數(shù)據(jù)庫上,這同樣是高吞吐量的保障。
可觀察及統(tǒng)計(jì)
雖然已經(jīng)對整體應(yīng)用程序進(jìn)行更健壯的封裝,但是依然存在很多復(fù)雜性。為了管理如此規(guī)模的應(yīng)用程序,工具也是個(gè)煞費(fèi)苦心的領(lǐng)域。鑒于新服務(wù)的發(fā)布速度,快速收集每個(gè)服務(wù)運(yùn)行數(shù)據(jù)的能力是必不可少的。一般情況下,我們希望做數(shù)據(jù)驅(qū)動策略,所以必須無縫獲得盡可能詳細(xì)的數(shù)據(jù)。
鑒于我們需要在一個(gè)不斷變大的系統(tǒng)上建立越來越多的服務(wù),我們必須讓這個(gè)操作變得非常簡單。為此,Runtime Systems團(tuán)隊(duì)為工程師建立了兩個(gè)工具:Viz和Zipkin。這兩個(gè)工具都對Finagle可見并與之整合,這樣所有使用Finagle建立的系統(tǒng)都可以自動通信。
1
2
3
|
stats.timeFuture( "request_latency_ms" ) { // dispatch to do work } |
如上顯示,一個(gè)服務(wù)通過短短的3行代碼就可以給Viz報(bào)告統(tǒng)計(jì)信息。這樣一來,任何人使用Viz都可以查詢自己感興趣的數(shù)據(jù),比如request_latency_ms中第55和99百分位數(shù)據(jù)。
運(yùn)行時(shí)配置和測試
***,當(dāng)我們實(shí)現(xiàn)以上所有步驟時(shí),卻遭遇了兩個(gè)看似無關(guān)緊要的障礙:發(fā)布特性必須在不同的服務(wù)之間做一系列協(xié)調(diào),而Twitter的規(guī)模之也導(dǎo)致服務(wù)階段測試能力的喪失。依賴部署去開啟用戶層代碼已不再可行,取而代之的是必須在整個(gè)應(yīng)用程序上做協(xié)調(diào);此外,考慮到Twitter的相對體積,在整個(gè)隔離環(huán)境下進(jìn)行有意義的測試變得非常困難。相對而言,我們并沒有在隔離系統(tǒng)中進(jìn)行測試——我們使用了運(yùn)行時(shí)配置來應(yīng)對。
我們在服務(wù)中整合了被稱為Decider的系統(tǒng),它允在服務(wù)中改變發(fā)生時(shí),以近實(shí)時(shí)的方式將這個(gè)改變擴(kuò)散到整個(gè)系統(tǒng)。這意味著一旦準(zhǔn)備就緒,軟件和多個(gè)系統(tǒng)就可以投入使用。然而一個(gè)特性不應(yīng)該是“神出鬼沒”的,Decider同樣允許我們靈活的去做基于二進(jìn)制或者百分位的轉(zhuǎn)換,比如讓某個(gè)特性只針對百分之多少的傳輸或者用戶生效。我們可以將代碼配置成完全“off”或者是安全設(shè)置,然后逐漸的將它打開或者關(guān)閉,直到可以確信它正常工作并且系統(tǒng)可以支撐新的負(fù)載。這樣做可以降低隊(duì)伍之間協(xié)調(diào)的工作量,取而代之的是我們在運(yùn)行時(shí)進(jìn)行設(shè)置。
Twitter系統(tǒng)的現(xiàn)狀
比之3年前,當(dāng)下Twitter系統(tǒng)的性能、有效性和可靠性都得到了顯著提高,從下圖不難看到,在集群使用率在50%到99.9%的情況下,網(wǎng)站的性能都得到了大幅度的提升,然而機(jī)器的數(shù)量卻減少了5至12倍。過去6個(gè)月,服務(wù)的可用性更達(dá)到了4個(gè)9(99.99%)。

模仿軟件堆棧去設(shè)置工程團(tuán)隊(duì),讓團(tuán)隊(duì)長期獲得一個(gè)服務(wù)的所有權(quán),有助于這部分基礎(chǔ)設(shè)施專家的形成。團(tuán)隊(duì)有著自己的接口及問題域,舉個(gè)例子:不是所有的團(tuán)隊(duì)都需要去關(guān)心tweet的擴(kuò)展問題,只有涉及tweet子系統(tǒng)(tweet服務(wù)團(tuán)隊(duì)、儲存團(tuán)隊(duì)、緩存團(tuán)隊(duì)等)運(yùn)行的團(tuán)隊(duì)必須去擴(kuò)展tweet的寫入和讀出,其它的團(tuán)隊(duì)只需要懂得使用現(xiàn)成的API就可以了。