背景補充:日本網(wǎng)民一直都有在電視節(jié)目播出的同時,在網(wǎng)絡(luò)平臺上吐槽或跟隨片中角色喊出臺詞的習(xí)慣,被稱作“實況”行為。宮崎駿監(jiān)督的名作動畫《天空之城》于2013年8月2日晚在NTV電視臺迎來14次電視重播。當劇情發(fā)展到男女主角巴魯和希達共同念出毀滅之咒“Blase”時,眾多網(wǎng)友也在推特上同時發(fā)出這條推特,創(chuàng)造了每秒推特發(fā)送數(shù)量的新紀錄。
根據(jù)推特日本官方帳號,當?shù)貢r間8月2日晚11時21分50秒,因為“Blase祭”的影響,推特發(fā)送峰值達到了143,199次/秒。這一數(shù)字高于此前推特發(fā)送峰值的最高紀錄,2013年日本時區(qū)新年時的33,388次/秒。更高于拉登之死(5106次/秒)、東日本大地震(5530次/秒)、美國流行天后碧昂斯宣布懷孕(8868次/秒)。
下圖是峰值發(fā)生的鄰近時間段的訪問頻率圖,Twitter通常每天的推文數(shù)是 5 億條,平均下來每秒大概產(chǎn)生5700條。這個峰值大概是穩(wěn)定狀態(tài)下訪問量的25倍!

在這個峰值期間,用戶并沒有感覺到暫時性的功能異常。無論世界上發(fā)生了什么,Twitter始終在你身邊,這是Twitter的目標之一。
“新的Tweets峰值誕生:143,199次Tweets每秒。通常情況:5億次每天;平均值5700Tweets每秒”
這個目標在3年前還是遙不可及的,2010年世界杯直接把Twitter變成了全球即時溝通的中心。每一次射門、罰球、黃牌或者紅牌,用戶都在發(fā)推文,這反復(fù)地消耗著系統(tǒng)帶寬,從而使其在短時間內(nèi)無法訪問。工程師們在這期間徹夜工作,拼命想找到并實現(xiàn)一種方法可以把整個系統(tǒng)的負載提升一個量級。不幸的是,這些性能的提升很快被Twitter用戶的快速增長所淹沒,工程師們已經(jīng)開始感到黔驢技窮了。
經(jīng)歷了那次慘痛的經(jīng)歷,Twitter決定要回首反思。那時Twitter決定了要重新設(shè)計Twitter,讓它能搞定持續(xù)增長的訪問負載,并保證平穩(wěn)運行。從那開始Twitter做了很大努力,來保證面臨世界各地發(fā)生的熱點事件時,Twitter仍能提供穩(wěn)定的服務(wù)。Twitter現(xiàn)在已經(jīng)能扛住諸如播放“天空之城”,舉辦超級碗,慶祝新年夜等重大事件帶來的訪問壓力。重新設(shè)計/架構(gòu),不但使系統(tǒng)在突發(fā)訪問峰值期間的穩(wěn)定性得到了保證,還提供了一個可伸縮的平臺,從而使新特性更容易構(gòu)建,其中包括不同設(shè)備間同步消息,使Tweets包含更豐富內(nèi)容的Twitter卡,包含用戶和故事的富搜索體驗等等特性。其他更多的特性也即將呈現(xiàn)。
開始重新架構(gòu)
2010年世界杯塵埃落定,Twitter總覽了整個項目,并有如下的發(fā)現(xiàn):
Twitter正運行著世界上最大的Ruby on Rails集群,Twitter非??焖俚耐七M系統(tǒng)的演進–在那時,大概200個工程師為此工作,無論是新用戶數(shù)還是絕對負載都在爆炸式的增長,這個系統(tǒng)沒有倒下。它還是一個統(tǒng)一的整體,Twitter的所有工作都在其上運行,從管理純粹的數(shù)據(jù)庫,memcache連接,站點的渲染,暴露共有API這些都集中在一個代碼庫上。這不但增加了程序員搞清整個系統(tǒng)的難度,也使管理和同步各個項目組變得更加困難。
Twitter的存儲系統(tǒng)已經(jīng)達到閾值–Twitter依賴的MySQL存儲系統(tǒng)是臨時切分的,它只有一個單主節(jié)點。這個系統(tǒng)在消化/處理快速涌現(xiàn)的tweets時會陷入麻煩,Twitter在運營時不得不不斷的增加新的數(shù)據(jù)庫。Twitter的所有數(shù)據(jù)庫都處于讀寫的熱點中。
Twitter面臨問題時,只是一味的靠扔進更多的機器來扛住,并沒有用工程的方式來解決它–根據(jù)機器的配置,前端Ruby機器的每秒事務(wù)處理數(shù)遠沒有達到Twitter預(yù)定的能力。從以往的經(jīng)驗,Twitter知道它應(yīng)該能處理更多的事務(wù)。
最后,從軟件的角度看,Twitter發(fā)現(xiàn)自己被推到了一個”優(yōu)化的角落“,在那Twitter以代碼的可讀性和可擴展性為代價來換取性能和效率的提升。
結(jié)論是Twitter應(yīng)該開啟一個新工程來重新審視Twitter的系統(tǒng)。Twitter設(shè)立了三個目標來激勵自己。
Twitter一直都需要一個高屋建瓴的建構(gòu)來確保性能/效率/可靠性,Twitter想要保證在正常情況下有較好的平均系統(tǒng)響應(yīng)時間,同時也要考慮到異常峰值的情況,這樣才能保證在任何時間都能提供一致的服務(wù)和用戶體驗。Twitter要把機器的需求量降低10倍,還要提高容錯性,把失敗進行隔離以避免更大范圍的服務(wù)中斷–這在機器數(shù)量快速增長的背景下尤為重要,因為機器數(shù)的快速增長也意味著單體機器故障的可能性在增加。系統(tǒng)中出現(xiàn)失敗是不可避免的,Twitter要做的是使整個系統(tǒng)處于可控的狀態(tài)。
Twitter要劃清相關(guān)邏輯間的界限,整個公司工作在一個的代碼庫上的方式把Twitter搞的很慘,所以Twitter開始嘗試以基于服務(wù)的松耦合的模式進行劃分模塊。Twitter曾經(jīng)的目標是鼓勵封裝和模塊化的最佳實踐,但這次Twitter把這個觀點深入到了系統(tǒng)層次,而不是類/模塊或者包層。
最重要的是要更快的啟動新特性。以小并自主放權(quán)的團隊模式展開工作,他們可以內(nèi)部決策并發(fā)布改變給用戶,這是獨立于其他團隊的。
針對上面的要求,Twitter構(gòu)建了原型來證明重新架構(gòu)的思路。Twitter并沒有嘗試所有的方面,并且即使Twitter嘗試的方面在最后也可能并像計劃中那樣管用。但是,Twitter已經(jīng)能夠設(shè)定一些準則/工具/架構(gòu),這些使Twitter到達了一個憧憬中的更靠譜的狀態(tài)。
The JVM VS the Ruby VM
首先,Twitter在三個維度上評估了前端服務(wù)節(jié)點:CPU,內(nèi)存和網(wǎng)絡(luò)?;赗uby的機器在CPU和內(nèi)存方面遭遇瓶頸–但是Twitter并未處理預(yù)計中那么多的負載,并且網(wǎng)絡(luò)帶寬也沒有接近飽和。Twitter的Rails服務(wù)器在那時還不得不設(shè)計成單線程并且一次處理一個請求。每一個Rails主機跑在一定數(shù)量的Unicorn處理器上來提供主機層的并發(fā),但此處的復(fù)制被轉(zhuǎn)變成了資源的浪費(這里譯者沒太理清,請高手矯正,我的理解是Rails服務(wù)在一臺機器上只能單線程跑,這浪費了機器上多核的資源)。歸結(jié)到最后,Rails服務(wù)器就只能提供200~300次請求每秒的服務(wù)。
Twitter的負載總是增長的很快,做個數(shù)學(xué)計算就會發(fā)現(xiàn)搞定不斷增長的需求將需要大量的機器。
在那時,Twitter有著部署大規(guī)模JVM服務(wù)的經(jīng)驗,Twitter的搜索引擎是用Java寫的,Twitter的流式API的基礎(chǔ)架構(gòu)還有Twitter的社交圖譜系統(tǒng)Flock都是用Scala實現(xiàn)的。Twitter著迷于JVM提供的性能。在Ruby虛擬機上達到Twitter要求的性能/可靠性/效率的目標不是很容易,所以Twitter著手開始寫運行在JVM上的代碼。Twitter評估了這帶來的好處,在同樣的硬件上,重寫Twitter的代碼能給Twitter帶來10倍的性能改進–現(xiàn)今,Twitter單臺服務(wù)器達到了每秒10000-20000次請求的處理能力。
Twitter對JVM存在相當程度的信任,這是因為很多人都來自那些運營/調(diào)配著大規(guī)模JVM集群的公司。Twitter有信心使Twitter在JVM的世界實現(xiàn)巨變?,F(xiàn)在Twitter不得不解耦Twitter的架構(gòu)從而找出這些不同的服務(wù)如何協(xié)作/通訊。
編程模型
在Twitter的Ruby系統(tǒng)中,并行是在進程的層面上管理的:一個單個請求被放進某一進程的隊列中等待處理。這個進程在請求的處理期間將完全被占用。這增加了復(fù)雜性,這樣做實際上使Twitter變成一個單個服務(wù)依賴于其他服務(wù)的回復(fù)的架構(gòu)。基于Ruby的進程是單線程的,Twitter的響應(yīng)時間對后臺系統(tǒng)的響應(yīng)非常敏感,二者緊密關(guān)聯(lián)。Ruby提供了一些并發(fā)的選項,但是那并沒有一個標準的方法去協(xié)調(diào)所有的選項。JVM則在概念和實現(xiàn)中都灌輸了并發(fā)的支持,這使Twitter可以真正的構(gòu)建一個并發(fā)的編程平臺。
針對并發(fā)提供單個/統(tǒng)一的方式已經(jīng)被證明是有必要的,這個需求在處理網(wǎng)絡(luò)請求是尤為突出。Twitter都知道,實現(xiàn)并發(fā)的代碼(包括并發(fā)的網(wǎng)絡(luò)處理代碼)是個艱巨的任務(wù),它可以有多種實現(xiàn)方式。事實上,Twitter已經(jīng)開始碰到這些問題了。當Twitter開始把系統(tǒng)解耦成服務(wù)時,每一個團隊都或多或少的采用了不盡相同的方式。例如,客戶端到服務(wù)的失效并沒有很好的交互:這是由于Twitter沒有一致的后臺抗壓機制使服務(wù)器返回某值給客戶端,這導(dǎo)致了Twitter經(jīng)歷了野牛群狂奔式的瘋狂請求,客戶端猛戳延遲的服務(wù)。這些失效的區(qū)域警醒Twitter–擁有一個統(tǒng)一完備的客戶/服務(wù)器間的庫來包含連接池/失效策略/負載均衡是非常重要的。為了把這個理念深入人心,Twitter引入了”Futures and Finagle”協(xié)議。
現(xiàn)在,Twitter不僅有了一致的做事手段,Twitter還把系統(tǒng)需要的所有東西都包含進核心的庫里,這樣Twitter開新項目時就會進展飛速。同時,Twitter現(xiàn)在不需要過多的擔(dān)心每個系統(tǒng)是如何運行,從而可以把更多的經(jīng)歷放到應(yīng)用和服務(wù)的接口上。
獨立的系統(tǒng)
Twitter實施了架構(gòu)上的重大改變,把集成化的Ruby應(yīng)用變成一個基于服務(wù)的架構(gòu)。Twitter集中力量創(chuàng)建了Tweet時間線和針對用戶的服務(wù)–這是Twitter的核心所在。這個改變帶給組織更加清晰的邊界和團隊級別的責(zé)任制與獨立性。在Twitter古老的整體/集成化的世界,Twitter要么需要一個了解整個工程的大牛,要么是對某一個模塊或類清楚的代碼所有者。
悲劇的是,代碼膨脹的太快了,找到了解所有模塊的大牛越來越難,然而實踐中,僅僅依靠幾個對某一模塊/類清楚的代碼作者又不能搞定問題。Twitter的代碼庫變得越來越難以維護,各個團隊常常要像考古一樣把老代碼翻出來研究才能搞清楚某一功能。不然,Twitter就組織類似“捕鯨征程”的活動,耗費大量的人力來搞出大規(guī)模服務(wù)失效的原因。往往一天結(jié)束,Twitter花費了大量的時間在這上面,而沒有精力來開發(fā)/發(fā)布新特性,這讓Twitter感覺很糟。
Twitter的理念曾經(jīng)并一直都是–一個基于服務(wù)的架構(gòu)可以讓Twitter并行的開發(fā)系統(tǒng)–Twitter就網(wǎng)絡(luò)RPC接口達成一致,然后各自獨立的開發(fā)系統(tǒng)的內(nèi)部實現(xiàn)–但,這也意味著系統(tǒng)的內(nèi)部邏輯是自耦合的。如果Twitter需要針對Tweets進行改變,Twitter可以在某一個服務(wù)例如Tweets服務(wù)進行更改,然后這個更改會在整個架構(gòu)中得到體現(xiàn)。然而在實踐中,Twitter發(fā)現(xiàn)不是所有的組都在以同樣的方式規(guī)劃變更:例如一個在Tweet服務(wù)的變更要使Tweet的展現(xiàn)改變,那么它可能需要其他的服務(wù)先進行更新以適應(yīng)這個變化。權(quán)衡利弊,這種理念還是為Twitter贏得了更多的時間。

這個系統(tǒng)架構(gòu)也反映了Twitter一直想要的方式,并且使Twitter的工程組織有效的運轉(zhuǎn)。工程團隊建立了高度自耦合的小組并能夠獨立/快速的展開工作。這意味著Twitter傾向于讓項目組啟動運行自己的服務(wù)并調(diào)用后臺系統(tǒng)來完成任務(wù)。這實際也暗含了大量運營的工作。
存儲
即使Twitter把Twitter板結(jié)成一坨的系統(tǒng)拆開成服務(wù),存儲仍然是一個巨大的瓶頸。Twitter在那時還把tweets存儲在一個單主的MySQL數(shù)據(jù)庫中。Twitter采用了臨時數(shù)據(jù)存儲的策略,數(shù)據(jù)庫中的每一行是一個tweet,Twitter把tweet有序的存儲在數(shù)據(jù)庫中,當一個庫滿了Twitter就新開一個庫然后重配軟件開始往新庫中添加數(shù)據(jù)。這個策略為Twitter節(jié)省了一定的時間,但是面對突發(fā)的高訪問量,Twitter仍然一籌莫展,因為大量的數(shù)據(jù)需要被串行化到一個單個的主數(shù)據(jù)庫中以至于Twitter幾臺局部的數(shù)據(jù)庫會發(fā)生高強度的讀請求。Twitter得為Tweet存儲設(shè)計一個不同的分區(qū)策略。
Twitter引入了Gizzard并把它應(yīng)用到了tweets,它可以創(chuàng)建分片并容錯的分布式數(shù)據(jù)庫。Twitter創(chuàng)造了T-Bird(沒懂啥意思,意思是Twitter的速度快起來了?)。這樣,Gizzard充當了MySQL集群的前端,每當一個tweet抵達系統(tǒng),Gizzard對其進行哈希計算,然后選擇一個適當?shù)臄?shù)據(jù)庫進行存儲。當然,這意味著Twitter失去了依靠MySQL產(chǎn)生唯一ID的功能。Snowflake很好的解決了上述問題。Snowflake使Twitter能夠創(chuàng)建一個幾乎可以保證全局唯一的ID。Twitter依靠它產(chǎn)生新的tweet ID,作為代價,Twitter將沒有“把某數(shù)加1產(chǎn)生新ID”的功能。一旦Twitter得到一個IDTwitter靠Gizzard來存儲它。假設(shè)Twitter的哈希算法足夠好,從而Twitter的tweets是接近于均勻的分布于各個儲存的,Twitter就能夠?qū)崿F(xiàn)用同樣數(shù)量的數(shù)據(jù)庫承載更多的數(shù)據(jù)。Twitter的讀請求同樣也接近平均的分布于整個分布式集群中,這也增加了Twitter的吞度量。
可觀察性和可統(tǒng)計性
把那坨脆弱的板結(jié)到一起的系統(tǒng)變成一個更健壯的/良好封裝的/但也蠻復(fù)雜的/基于服務(wù)的應(yīng)用。Twitter不得不搞出一些工具來使管理這頭野獸變得可能?;诖蠹叶荚诳焖俚臉?gòu)建各種服務(wù),Twitter需要一種可靠并簡單的方式來得到這些服務(wù)的運行情況的數(shù)據(jù)。數(shù)據(jù)為王是默認準則,Twitter需要是使獲取上述的數(shù)據(jù)變得非常容易。
當Twitter將要在一個快速增長的巨大系統(tǒng)上啟動越來越多的服務(wù),Twitter必須使這種工作變得輕松。運行時系統(tǒng)組開發(fā)為大家開發(fā)了兩個工具:Viz和Zipkin。二者都暴露并集成到了Finagle,所以所有基于Finagle的服務(wù)都可以自動的獲取到它們。
stats.timeFuture("request_latency_ms") {
// dispatch to do work
}
上面的代碼就是一個服務(wù)生成統(tǒng)計報告給Via所需做的唯一事情。從那里,任何Viz用戶都可以寫一個查詢來生成針對一些有趣的數(shù)據(jù)的時間/圖表,例如第50%和第99%的request_latency_ms。
運行時配置和測試
最后,當Twitter把所有的好東西放一起時,兩個看似無關(guān)的問題擺在面前:第一,整個系統(tǒng)的啟動需要協(xié)調(diào)多個系列的不同的服務(wù),Twitter沒有一個地方可以把Twitter這個量級的應(yīng)用所需要的服務(wù)弄到一起。Twitter已經(jīng)不能依靠通過部署來把新特性展現(xiàn)給客戶,應(yīng)用中的各個服務(wù)需要協(xié)調(diào)。第二,Twitter已經(jīng)變得太龐大,在一個完全封閉的環(huán)境下測試整個系統(tǒng)變得越來越困難。相對而言,Twitter測試自己孤立的系統(tǒng)是沒有問題的–所以Twitter需要一個辦法來測試大規(guī)模的迭代。Twitter接納了運行時配置。
Twitter通過一個稱作Decider的系統(tǒng)整合所有的服務(wù)。當有一個變更要上線,它允許Twitter只需簡單開啟一個開關(guān)就可以讓架構(gòu)上的多個子系統(tǒng)都和這個改變進行幾乎即時的交互。這意味著軟件和多個系統(tǒng)可以在團隊認為成熟的情況下產(chǎn)品化,但其中的某一個特性不需要已經(jīng)被激活。Decider還允許Twitter進行二進制或百分比的切換,例如讓一個特性只針對x%的用戶開放。Twitter還可以先把完全未激活并完全安全的特性部署上線,然后梯度的開啟/關(guān)閉,知道Twitter有足夠的自信保證特性可以正確的運行并且系統(tǒng)可以負擔(dān)這個新的負荷。所有這些努力都可以減輕Twitter進行團隊之間溝通協(xié)調(diào)的活動,取而代之Twitter可以在系統(tǒng)運行時做Twitter想要的定制/配置。