解剖Twitter:Twitter系統架構設計分析
隨著信息爆炸的加劇,微博客網站Twitter橫空出世了。用橫空出世這個詞來形容Twitter的成長,并不夸張。從2006年5月Twitter上線,到2007年12月,一年半的時間里,Twitter用戶數從0增長到6.6萬。又過了一年,2008年12月,Twitter的用戶數達到5百萬。[1]
Twitter網站的成功,先決條件是能夠同時給千萬用戶提供服務,而且提供服務的速度要快。[2,3,4]
有觀點認為,Twitter的業務邏輯簡單,所以競爭門檻低。前半句正確,但是后半句有商榷余地。Twitter的競爭力,離不開嚴謹的系統架構設計。
【1】萬事開頭易
Twitter的核心業務邏輯,在于Following和Be followed。[5]
進入Twitter個人主頁,你會看到你following的那些作者,最近發表的微博客。所謂微博客,就是一則短信,Twitter規定,短信的長度不得超過140個字。短信不僅可以包含普通文字信息,也可以包含URL,指向某個網頁,或者照片及視頻等等。這就是following的過程。
當你寫了一則短信并發表以后,你的followers會立刻在他們的個人主頁中看到你寫的最新短信。這就是be followed的過程。
實現這個業務流程似乎很容易。
1. 為每一個注冊用戶訂制一個Be-followed的表,主要內容是每一個follower的ID。同時,也訂制一個Following的表,主要內容是每一個following作者的ID。
2. 當用戶打開自己的個人空間時,Twitter先查閱Following表,找到所有following的作者的ID。然后去數據庫讀取每一位作者最近寫的短信。匯總后按時間順序顯示在用戶的個人主頁上。
3. 當用戶寫了一則短信時,Twitter先查閱Be-followed表,找到所有followers的IDs。然后逐個更新那些followers的主頁。
如果有follower正在閱讀他的Twitter個人主頁,主頁里暗含的JavaScript會自動每隔幾十秒,訪問一下Twitter服務器,檢查正在看的這個個人主頁是否有更新。如果有更新,立刻下載新的主頁內容。這樣follower就能讀到最新發表的短信了。
從作者發表到讀者獲取,中間的延遲,取決于JavaScript更新的間隔,以及Twitter服務器更新每個follower的主頁的時間。
從系統架構上來說,似乎傳統的三段論(Three-tier architecture [6]),足夠滿足這個業務邏輯的需要。事實上,最初的Twitter系統架構,的確就是三段論。
Reference:
[1] Fixing Twitter. (http://www.bookfm.com/courseware/coursewaredetail.html?cid=100777)
[2] Twitter blows up at SXSW conference. (http://gawker.com/tech/next-big-thing/twitter-blows-up-at-sxsw-conference-243634.php)
[3] First Hand Accounts of Terrorist Attacks in India on Twitter and Flickr. (http://www.techcrunch.com/2008/11/26/first-hand-accounts-of-terrorist-attacks-in-india-on-twitter/)
[4] Social Media Takes Center Stage in Iran. (http://www.findingdulcinea.com/news/technology/2009/June/Twitter-on-Iran-a-Go-to-Source-or-Almost-Useless.html)
[5] Twitter的這些那些. (http://www.ccthere.com/article/2363334) (http://www.ccthere.com/article/2369092)
[6] Three tier architecture.?http://en.wikipedia.org/wiki/Multitier_architecture
【2】三段論
網站的架構設計,傳統的做法是三段論。所謂“傳統的”,并不等同于“過時的”。大型網站的架構設計,強調實用。新潮的設計,固然吸引人,但是技術可能不成熟,風險高。所以,很多大型網站,走的是穩妥的傳統的路子。
2006年5月Twitter剛上線的時候,為了簡化網站的開發,他們使用了Ruby-On-Rails工具,而Ruby-On-Rails的設計思想,就是三段論。
1. 前段,即表述層(Presentation Tier) 用的工具是Apache Web Server,主要任務是解析HTTP協議,把來自不同用戶的,不同類型的請求,分發給邏輯層。
2. 中段,即邏輯層 (Logic Tier)用的工具是Mongrel Rails Server,利用Rails現成的模塊,降低開發的工作量。
3. 后段,即數據層 (Data Tier) 用的工具是MySQL 數據庫。
先說后段,數據層。
Twitter 的服務,可以概括為兩個核心,1. 用戶,2. 短信。用戶與用戶之間的關系,是追與被追的關系,也就是Following和Be followed。對于一個用戶來說,他只讀自己“追”的那些人寫的短信。而他自己寫的短信,只有那些“追”自己的人才會讀。抓住這兩個核心,就不難理解 Twitter的其它功能是如何實現的[7]。
圍繞這兩個核心,就可以著手設計Data Schema,也就是存放在數據層(Data Tier)中的數據的組織方式。不妨設置三個表[8],
1. 用戶表:用戶ID,姓名,登錄名和密碼,狀態(在線與否)。
2. 短信表:短信ID,作者ID,正文(定長,140字),時間戳。
3. 用戶關系表,記錄追與被追的關系:用戶ID,他追的用戶IDs (Following),追他的用戶IDs (Be followed)。
再說中段,邏輯層。
當用戶發表一條短信的時候,執行以下五個步驟,
1. 把該短信記錄到“短信表” 中去。
2. 從“用戶關系表”中取出追他的用戶的IDs。
3. 有些追他的用戶目前在線,另一些可能離線。在線與否的狀態,可以在“用戶表”中查到。過濾掉那些離線的用戶的IDs。
4. 把那些追他的并且目前在線的用戶的IDs,逐個推進一個隊列(Queue)中去。
5. 從這個隊列中,逐個取出那些追他的并且目前在線的用戶的IDs,并且更新這些人的主頁,也就是添加最新發表的這條短信。
以上這五個步驟,都由邏輯層(Logic Tier)負責。前三步容易解決,都是簡單的數據庫操作。最后兩步,需要用到一個輔助工具,隊列。隊列的意義在于,分離了任務的產生與任務的執行。
隊列的實現方式有多種,例如Apache Mina[9]就可以用來做隊列。但是Twitter團隊自己動手實現了一個隊列,Kestrel [10,11]。Mina與Kestrel,各自有什么優缺點,似乎還沒人做過詳細比較。
不管是Kestrel還是Mina,看起來都很復雜?;蛟S有人問,為什么不用簡單的數據結構來實現隊列,例如動態鏈表,甚至靜態數組?如果邏輯層只在一臺服務器上運行,那么對動態鏈表和靜態數組這樣的簡單的數據結構,稍加改造,的確可以當作隊列使用。Kestrel和Mina這些“重量級”的隊列,意義在于支持聯絡多臺機器的,分布式的隊列。在本系列以后的篇幅中,將會重點介紹。
最后說說前段,表述層。
表述層的主要職能有兩 個,1. HTTP協議處理器(HTTP Processor),包括拆解接收到的用戶請求,以及封裝需要發出的結果。2. 分發器(Dispatcher),把接收到的用戶請求,分發給邏輯層的機器處理。如果邏輯層只有一臺機器,那么分發器無意義。但是如果邏輯層由多臺機器組成,什么樣的請求,發給邏輯層里面哪一臺機器,就大有講究了。邏輯層里眾多機器,可能各自專門負責特定的功能,而在同功能的機器之間,要分攤工作,使負載均衡。
訪問Twitter網站的,不僅僅是瀏覽器,而且還有手機,還有像QQ那樣的電腦桌面工具,另外還有各式各樣的網站插件,以便把其它網站聯系到Twitter.com上來[12]。因此,Twitter的訪問者與Twitter網站之間的通訊協議,不一定是HTTP,也存在其它協議。
三段論的Twitter架構,主要是針對HTTP協議的終端。但是對于其它協議的終端,Twitter的架構沒有明顯地劃分成三段,而是把表述層和邏輯層合二為一,在Twitter的文獻中,這二合一經常被稱為“API”。
綜上所述,一個能夠完成Twitter基本功能的,簡單的架構如Figure 1 所示?;蛟S大家會覺得疑惑,這么出名的網站,架構就這么簡單?Yes and No,2006年5月Twitter剛上線的時候,Twitter架構與Figure 1差距不大,不一樣的地方在于加了一些簡單的緩存(Cache)。即便到了現在,Twitter的架構依然可以清晰地看到Figure 1 的輪廓。
Figure 1. The essential 3-tier of Twitter architecture
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/d22c_4051785892_e677ae9d33_o.png
Reference,
[7] Tweets中常用的工具 (http://www.ccthere.com/article/2383833)
[8] 構建基于PHP的微博客服務 (http://webservices.ctocio.com.cn/188/9092188.shtml)
[9] Apache Mina Homepage (http://mina.apache.org/)
[10] Kestrel Readme (http://github.com/robey/kestrel)
[11] A Working Guide to Kestrel. (http://github.com/robey/kestrel/blob/master/docs/guide.md)
[12] Alphabetical List of Twitter Services and Applications (http://en.wikipedia.org/wiki/List_of_Twitter_services_and_applications)
【3】Cache == Cash
Cache == Cash,緩存等于現金收入。雖然這話有點夸張,但是正確使用緩存,對于大型網站的建設,是至關重要的大事。網站在回應用戶請求時的反應速度,是影響用戶體驗的一大因素。而影響速度的原因有很多,其中一個重要的原因在于硬盤的讀寫(Disk IO)。
Table 1 比較了內存(RAM),硬盤(Disk),以及新型的閃存(Flash),在讀寫方面的速度比較。硬盤的讀寫,速度比內存的慢了百萬倍。所以,要提高網站的速度,一個重要措施是盡可能把數據緩存在內存里。當然,在硬盤里也必須保留一個拷貝,以此防范萬一由于斷電,內存里的數據丟失的情況發生。
Table 1. Storage media comparison of Disk, Flash and RAM [13]
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/9d42_4060534279_f575212c12_o.png
Twitter 工程師認為,一個用戶體驗良好的網站,當一個用戶請求到達以后,應該在平均500ms以內完成回應。而Twitter的理想,是達到200ms- 300ms的反應速度[17]。因此在網站架構上,Twitter大規模地,多層次多方式地使用緩存。Twitter在緩存使用方面的實踐,以及從這些實踐中總結出來的經驗教訓,是Twitter網站架構的一大看點。
Figure 2. Twitter architecture with Cache
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/0ea9_4065827637_bb2ccc8e3f_o.png
哪里需要緩存?越是Disk IO頻繁的地方,越需要緩存。
前面說到,Twitter業務的核心有兩個,用戶和短信(Tweet)。圍繞這兩個核心,數據庫中存放著若干表,其中最重要的有三個,如下所示。這三個表的設置,是旁觀者的猜測,不一定與Twitter的設置完全一致。但是萬變不離其宗,相信即便有所不同,也不會本質區別。
1. 用戶表:用戶ID,姓名,登錄名和密碼,狀態(在線與否)。
2. 短信表:短信ID,作者ID,正文(定長,140字),時間戳。
3. 用戶關系表,記錄追與被追的關系:用戶ID,他追的用戶IDs (Following),追他的用戶IDs (Be followed)。
有沒有必要把這幾個核心的數據庫表統統存放到緩存中去?Twitter的做法是把這些表拆解,把其中讀寫最頻繁的列放進緩存。
1. Vector Cache and Row Cache
具體來說,Twitter工程師認為最重要的列是IDs。即新發表的短信的IDs,以及被頻繁閱讀的熱門短信的IDs,相關作者的IDs,以及訂閱這些作者的讀者的IDs。把這些IDs存放進緩存 (Stores arrays of tweet pkeys [14])。在Twitter文獻中,把存放這些IDs的緩存空間,稱為Vector Cache [14]。
Twitter工程師認為,讀取最頻繁的內容是這些IDs,而短信的正文在其次。所以他們決定,在優先保證Vector Cache所需資源的前提下,其次重要的工作才是設立Row Cache,用于存放短信正文。
命中率(Hit Rate or Hit Ratio)是測量緩存效果的最重要指標。如果一個或者多個用戶讀取100條內容,其中99條內容存放在緩存中,那么緩存的命中率就是99%。命中率越高,說明緩存的貢獻越大。
設立Vector Cache和Row Cache后,觀測實際運行的結果,發現Vector Cache的命中率是99%,而Row Cache的命中率是95%,證實了Twitter工程師早先押注的,先IDs后正文的判斷。
Vector Cache和Row Cache,使用的工具都是開源的MemCached [15]。
2. Fragment Cache and Page Cache
前文說到,訪問Twitter網站的,不僅僅是瀏覽器,而且還有手機,還有像QQ那樣的電腦桌面工具,另外還有各式各樣的網站插件,以便把其它網站聯系到Twitter.com上來[12]。接待這兩類用戶的,是以Apache Web Server為門戶的Web通道,以及被稱為“API”的通道。其中API通道受理的流量占總流量的80%-90% [16]。
所以,繼Vector Cache和Row Cache以后,Twitter工程師們把進一步建筑緩存的工作,重點放在如何提高API通道的反應速度上。
讀者頁面的主體,顯示的是一條又一條短信。不妨把整個頁面分割成若干局部,每個局部對應一條短信。所謂Fragment,就是指頁面的局部。除短信外,其它內容例如Twitter logo等等,也是Fragment。如果一個作者擁有眾多讀者,那么緩存這個作者寫的短信的布局頁面(Fragment),就可以提高網站整體的讀取效率。這就是Fragment Cache的使命。
對于一些人氣很旺的作者,讀者們不僅會讀他寫的短信,而且會訪問他的主頁,所以,也有必要緩存這些人氣作者的個人主頁。這就是Page Cache的使命。
Fragment Cache和Page Cache,使用的工具也是MemCached。
觀測實際運行的結果,Fragment Cache的命中率高達95%,而Page Cache的命中率只有40%。雖然Page Cache的命中率低,但是它的內容是整個個人主頁,所以占用的空間卻不小。為了防止Page Cache爭奪Fragment Cache的空間,在物理部署時,Twitter工程師們把Page Cache分離到不同的機器上去。
3. HTTP Accelerator
解決了API通道的緩存問題,接下去Twitter工程師們著手處理Web通道的緩存問題。經過分析,他們認為Web通道的壓力,主要來自于搜索。尤其是面臨突發事件時,讀者們會搜索相關短信,而不理會這些短信的作者,是不是自己“追”的那些作者。
要降低搜索的壓力,不妨把搜索關鍵詞,及其對應的搜索結果,緩存起來。Twitter工程師們使用的緩存工具,是開源項目Varnish [18]。
比較有趣的事情是,通常把Varnish部署在Web Server之外,面向Internet的位置。這樣,當用戶訪問網站時,實際上先訪問Varnish,讀取所需內容。只有在Varnish沒有緩存相應內容時,用戶請求才被轉發到Web Server上去。而Twitter的部署,卻是把Varnish放在Apache Web Server內側[19]。原因是Twitter的工程師們覺得Varnish的操作比較復雜,為了降低Varnish崩潰造成整個網站癱瘓的可能性,他們便采取了這種古怪而且保守的部署方式。
Apache Web Server的主要任務,是解析HTTP,以及分發任務。不同的Mongrel Rails Server負責不同的任務,但是絕大多數Mongrel Rails Server,都要與Vector Cache和Row Cache聯系,讀取數據。Rails Server如何與MemCached聯系呢?Twitter工程師們自行開發了一個Rails插件(Gem),稱為CacheMoney。
雖然Twitter沒有公開Varnish的命中率是多少,但是[17]聲稱,使用了Varnish以后,導致整個Twitter.com網站的負載下降了50%,參見Figure 3.
Figure 3. Cache decreases Twitter.com load by 50% [17]
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/0b3a_4061273900_2d91c94374_o.png
Reference,
[12] Alphabetical List of Twitter Services and Applications.
(http://en.wikipedia.org/wiki/List_of_Twitter_services_and_applications)
[13] How flash changes the DBMS world.
(http://hansolav.net/blog/content/binary/HowFlashMemory.pdf)
[14] Improving running component of Twitter.
(http://qconlondon.com/london-2009/file?path=/qcon-london-2009/slides/
EvanWeaver_ImprovingRunningComponentsAtTwitter.pdf)
[15] A high-performance, general-purposed, distributed memory object caching system.
(http://www.danga.com/memcached/)
[16] Updating Twitter without service disruptions.
(http://gojko.net/2009/03/16/qcon-london-2009-upgrading-twitter-without-service-disruptions/)
[17] Fixing Twitter. (http://assets.en.oreilly.com/1/event/29/
Fixing_Twitter_Improving_the_Performance_and_Scalability_of_the_World_s_
Most_Popular_Micro-blogging_Site_Presentation%20Presentation.pdf)
[18] Varnish, a high-performance HTTP accelerator.
(http://varnish.projects.linpro.no/)
[19] How to use Varnish in Twitter.com?
(http://projects.linpro.no/pipermail/varnish-dev/2009-February/000968.html)
[20] CacheMoney Gem, an open-source write-through caching library.
(http://github.com/nkallen/cache-money)
【4】抗洪需要隔離
如果說如何巧用Cache是Twitter的一大看點,那么另一大看點是它的消息隊列(Message Queue)。為什么要使用消息隊列?[14]的解釋是“隔離用戶請求與相關操作,以便燙平流量高峰 (Move operations out of the synchronous request cycle, amortize load over time)”。
為了理解這段話的意思,不妨來看一個實例。2009年1月20日星期二,美國總統Barack Obama就職并發表演說。作為美國歷史上第一位黑人總統,Obama的就職典禮引起強烈反響,導致Twitter流量猛增,如Figure 4 所示。
Figure 4. Twitter burst during the inauguration of Barack Obama, 1/20/2009, Tuesday
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/d594_4071879010_19fb519124_o.png
其中洪峰時刻,Twitter網站每秒鐘收到350條新短信,這個流量洪峰維持了大約5分鐘。根據統計,平均每個Twitter用戶被120人“追”,這就 是說,這350條短信,平均每條都要發送120次 [16]。這意味著,在這5分鐘的洪峰時刻,Twitter網站每秒鐘需要發送350 x 120 = 42,000條短信。
面對洪峰,如何才能保證網站不崩潰?辦法是迅速接納,但是推遲服務。打個比方,在晚餐高峰時段,餐館常??蜐M。對于新來的顧客,餐館服務員不是拒之門外,而是讓這些顧客在休息廳等待。這就是[14] 所說的 “隔離用戶請求與相關操作,以便燙平流量高峰”。
如何實施隔離呢?當一位用戶訪問Twitter網站時,接待他的是Apache Web Server。Apache做的事情非常簡單,它把用戶的請求解析以后,轉發給Mongrel Rails Sever,由Mongrel負責實際的處理。而Apache騰出手來,迎接下一位用戶。這樣就避免了在洪峰期間,用戶連接不上Twitter網站的尷尬局面。
雖然Apache的工作簡單,但是并不意味著Apache可以接待無限多的用戶。原因是Apache解析完用戶請求,并且轉發給 Mongrel Server以后,負責解析這個用戶請求的進程(process),并沒有立刻釋放,而是進入空循環,等待Mongrel Server返回結果。這樣,Apache能夠同時接待的用戶數量,或者更準確地說,Apache能夠容納的并發的連接數量(concurrent connections),實際上受制于Apache能夠容納的進程數量。Apache系統內部的進程機制參見Figure 5,其中每個Worker代表一個進程。
Apache能夠容納多少個并發連接呢?[22]的實驗結果是4,000個,參見Figure 6。如何才能提高Apache的并發用戶容量呢?一種思路是不讓連接受制于進程。不妨把連接作為一個數據結構,存放到內存中去,釋放進程,直到 Mongrel Server返回結果時,再把這個數據結構重新加載到進程上去。
事實上Yaws Web Server[24],就是這么做的[23]。所以,Yaws能夠容納80,000以上的并發連接,這并不奇怪。但是為什么Twitter用 Apache,而不用Yaws呢?或許是因為Yaws是用Erlang語言寫的,而Twitter工程師對這門新語言不熟悉 (But you need in house Erlang experience [17])。
Figure 5. Apache web server system architecture [21]
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/3b81_4071355801_db6c8cd6c0_o.png
Figure 6. Apache vs. Yaws.
The horizonal axis shows the parallel requests,
the vertical one shows the throughput (KBytes/second).
The red curve is Yaws, running on NFS.
The blue one is Apache, running on NFS,
while the green one is also Apache but on a local file system.
Apache dies at about 4,000 parallel sessions,
while Yaws is still functioning at over 80,000 parallel connections. [22]
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/8fa1_4072077210_3c3a507a8a_o.jpg
Reference,
[14] Improving running component of Twitter.
(http://qconlondon.com/london-2009/file?path=/qcon-london-
2009/slides/EvanWeaver_ImprovingRunningComponentsAtTwitter.pdf)
[16] Updating Twitter without service disruptions.
(http://gojko.net/2009/03/16/qcon-london-2009-upgrading-
twitter-without-service-disruptions/)
[17] Fixing Twitter. (http://assets.en.oreilly.com/1/event/29/Fixing_Twitter_
Improving_the_Performance_and_Scalability_of_the_World_s_Most_Popular_
Micro-blogging_Site_Presentation%20Presentation.pdf)
[21] Apache system architecture.
(http://www.fmc-modeling.org/download/publications/
groene_et_al_2002-architecture_recovery_of_apache.pdf)
[22] Apache vs Yaws. (http://www.sics.se/~joe/apachevsyaws.html)
[23] 質疑Apache和Yaws的性能比較. (http://www.javaeye.com/topic/107476)
[24] Yaws Web Server. (http://yaws.hyber.org/)
[25] Erlang Programming Language. (http://www.erlang.org/)
【5】數據流與控制流
通過讓Apache進程空循環的辦法,迅速接納用戶的訪問,推遲服務,說白了是個緩兵之計,目的是讓用戶不至于收到“HTTP 503” 錯誤提示,“503錯誤” 是指 “服務不可用(Service Unavailable)”,也就是網站拒絕訪問。
大禹治水,重在疏導。真正的抗洪能力,體現在蓄洪和泄洪兩個方面。蓄洪容易理解,就是建水庫,要么建一個超大的水庫,要么造眾多小水庫。泄洪包括兩個方面,1. 引流,2. 渠道。
對于Twitter系統來說,龐大的服務器集群,尤其是以MemCached為主的眾多的緩存,體現了蓄洪的容量。引流的手段是Kestrel消息隊列,用于傳遞控制指令。渠道是機器與機器之間的數據傳輸通道,尤其是通往MemCached的數據通道。渠道的優劣,在于是否通暢。
Twitter的設計,與大禹的做法,形相遠,實相近。Twitter系統的抗洪措施,體現在有效地控制數據流,保證在洪峰到達時,能夠及時把數據疏散到多個機器上去,從而避免壓力過度集中,造成整個系統的癱瘓。
2009 年6月,Purewire公司通過爬Twitter網站,跟蹤Twitter用戶之間“追”與“被追”的關系,估算出Twitter用戶總量在 7,000,000左右 [26]。在這7百萬用戶中,不包括那些既不追別人,也不被別人追的孤立用戶。也不包括孤島人群,孤島內的用戶只相互追與被追,不與外界聯系。如果加上這 些孤立用戶和孤島用戶群,目前Twitter的用戶總數,或許不會超過1千萬。
截止2009年3月,中國移動用戶數已達 4.7億戶[27]。如果中國移動的飛信[28] 和139說客[29] 也想往Twitter方向發展,那么飛信和139的抗洪能力應該設計到多少呢?簡單講,需要把Twitter系統的現有規模,至少放大47倍。所以,有人 這樣評論移動互聯網產業,“在中國能做到的事情,在美國一定能做到。反之,不成立”。
但是無論如何,他山之石可以攻玉。這就是我們研究Twitter的系統架構,尤其是它的抗洪機制的目的。
Figure 7. Twitter internal flows
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/fe8f_4095392354_66bd4bcc30_o.png
下面舉個簡單的例子,剖析一下Twitter網站內部的流程,借此考察Twitter系統有哪些機制,去實現抗洪的三要素,“水庫”,“引流”和“渠道”。
假設有兩個作者,通過瀏覽器,在Twitter網站上發表短信。有一個讀者,也通過瀏覽器,訪問網站并閱讀他們寫的短信。
1. 作者的瀏覽器與網站建立連接,Apache Web Server分配一個進程(Worker Process)。作者登錄,Twitter查找作者的ID,并作為Cookie,記憶在HTTP郵包的頭屬性里。
2. 瀏覽器上傳作者新寫的短信(Tweet),Apache收到短信后,把短信連同作者ID,轉發給Mongrel Rails Server。然后Apache進程進入空循環,等待Mongrel的回復,以便更新作者主頁,把新寫的短信添加上去。
3. Mongrel收到短信后,給短信分配一個ID,然后把短信ID與作者ID,緩存到Vector MemCached服務器上去。
同時,Mongrel讓Vector MemCached查找,有哪些讀者“追”這位作者。如果Vector MemCached沒有緩存這些信息,Vector MemCached自動去MySQL數據庫查找,得到結果后,緩存起來,以備日后所需。然后,把讀者IDs回復給Mongrel。
接著,Mongrel把短信ID與短信正文,緩存到Row MemCached服務器上去。
4. Mongrel通知Kestrel消息隊列服務器,為每個作者及讀者開設一個隊列,隊列的名稱中隱含用戶ID。如果Kestrel服務器中已經存在這些隊列,那就延用以往的隊列。
對應于每個短信,Mongrel已經從Vector MemCached那里知道,有哪些讀者追這條短信的作者。Mongrel把這條短信的ID,逐個放進每位讀者的隊列,以及作者本人的隊列。
5. 同一臺Mongrel Server,或者另一臺Mongrel Server,在處理某個Kestrel隊列中的消息前,從這個隊列的名稱中解析出相應的用戶ID,這個用戶,既可能是讀者,也可能是作者。
然后Mongrel從Kestrel隊列中,逐個提取消息,解析消息中包含的短信ID。并從Row MemCached緩存器中,查找對應于這個短信ID的短信正文。
這時,Mongrel既得到了用戶的ID,也得到了短信正文。接下去Mongrel就著手更新用戶的主頁,添加上這條短信的正文。
6. Mongrel把更新后的作者的主頁,傳遞給正在空循環的Apache的進程。該進程把作者主頁主動傳送(push)給作者的瀏覽器。
如果讀者的瀏覽器事先已經登錄Twitter網站,建立連接,那么Apache給該讀者也分配了一個進程,該進程也處于空循環狀態。Mongrel把更新后的讀者的主頁,傳遞給相應進程,該進程把讀者主頁主動傳遞給讀者的瀏覽器。
咋一看,流程似乎不復雜?!八畮臁保耙鳌焙汀扒馈?,這抗洪三要素體現在哪里呢?盛名之下的Twitter,妙處何在?值得細究的看點很多。
Reference,
[26] Twitter user statistics by Purewire, June 2009.
(http://www.nickburcher.com/2009/06/twitter-user-statistics-purewire-report.html)
[27] 截止2009年3月,中國移動用戶數已達4.7億戶.
(http://it.sohu.com/20090326/n263018002.shtml)
[28] 中國移動飛信網. (http://www.fetion.com.cn/)
[29] 中國移動139說客網. (http://www.139.com/)
【6】流量洪峰與云計算
上一篇歷數了一則短信從發表到被閱讀,Twitter業務邏輯所經歷的6個步驟。表面上看似乎很乏味,但是細細咀嚼,把每個步驟展開來說,都有一段故事。
美國年度橄欖球決賽,綽號超級碗(Super Bowl)。Super Bowl在美國的收視率,相當于中國的央視春節晚會。2008年2月3日,星期天,該年度Super Bowl如期舉行。紐約巨人隊(Giants),對陣波士頓愛國者隊(Patriots)。這是兩支實力相當的球隊,決賽結果難以預料。比賽吸引了近一億美國人觀看電視實況轉播。
對于Twitter來說,可以預料的是,比賽進行過程中,Twitter流量必然大漲。比賽越激烈,流量越高漲。Twitter無法預料的是,流量究竟會漲到多少,尤其是洪峰時段,流量會達到多少。
根據[31]的統計,在Super Bowl比賽進行中,每分鐘的流量與當日平均流量相比,平均高出40%。在比賽最激烈時,更高達150%以上。與一周前,2008年1月27日,一個平靜的星期天的同一時段相比,流量的波動從平均10%,上漲到40%,最高波動從35%,上漲到150%以上。
Figure 8. Twitter traffic during Super Bowl, Sunday, Feb 3, 2008 [31]. The blue line represents the percentage of updates per minute during the Super Bowl normalized to the average number of updates per minute during the rest of the day, with spikes annotated to show what people were twittering about. The green line represents the traffic of a “regular” Sunday, Jan 27, 2008.
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/d4fa_4085122087_970072e518_o.png
由此可見,Twitter流量的波動十分可觀。對于Twitter公司來說,如果預先購置足夠的設備,以承受流量的變化,尤其是重大事件導致的洪峰流量,那么這些設備在大部分時間處于閑置狀態,非常不經濟。但是如果缺乏足夠的設備,那么面對重大事件,Twitter系統有可能崩潰,造成的后果是用戶流失。
怎么辦?辦法是變買為租。Twitter公司自己購置的設備,其規模以應付無重大事件時的流量壓力為限。同時租賃云計算平臺公司的設備,以應付重大事件來臨時的洪峰流量。租賃云計算的好處是,計算資源實時分配,需求高的時候,自動分配更多計算資源。
Twitter公司在2008年以前,一直租賃Joyent公司的云計算平臺。在2008年2月3日的Super Bowl即將來臨之際,Joyent答應Twitter,在比賽期間免費提供額外的計算資源,以應付洪峰流量[32]。但是詭異的是,離大賽只剩下不到4天,Twitter公司突然于1月30日晚10時,停止使用Joyent的云計算平臺,轉而投奔Netcraft [33,34]。
Twitter棄Joyent,投Netcraft,其背后的原因是商務糾葛,還是擔心Joyent的服務不可靠,至今仍然是個謎。
變買為租,應對洪峰,這是一個不錯的思路。但是租來的計算資源怎么用,又是一個大問題。查看一下[35],不難發現Twitter把租賃來的計算資源,大部分用于增加Apache Web Server,而Apache是Twitter整個系統的最前沿的環節。
為什么Twitter很少把租賃來的計算資源,分配給Mongrel Rails Server,MemCached Servers,Varnish HTTP Accelerators等等其它環節?在回答這個問題以前,我們先復習一下前一章“數據流與控制流”的末尾,Twitter從寫到讀的6個步驟。
這6個步驟的前2步說到,每個訪問Twitter網站的瀏覽器,都與網站保持長連接。目的是一旦有人發表新的短信,Twitter網站在500ms以內,把新短信push給他的讀者。問題是在沒有更新的時候,每個長連接占用一個Apache的進程,而這個進程處于空循環。所以,絕大多數Apache進程,在絕大多數時間里,處于空循環,因此占用了大量資源。
事實上,通過Apache Web Servers的流量,雖然只占Twitter總流量的10%-20%,但是Apache卻占用了Twitter整個服務器集群的50%的資源[16]。所以,從旁觀者角度來看,Twitter將來勢必罷黜Apache。但是目前,當Twitter分配計算資源時,迫不得已,只能優先保證Apache的需求。
迫不得已只是一方面的原因,另一方面,也表明Twitter的工程師們,對其系統中的其它環節,太有信心了。
在第四章“抗洪需要隔離”中,我們曾經打過一個比方,“在晚餐高峰時段,餐館常常客滿。對于新來的顧客,餐館服務員不是拒之門外,而是讓這些顧客在休息廳等待”。對于Twitter系統來說,Apache充當的角色就是休息廳。只要休息廳足夠大,就能暫時穩住用戶,換句行話講,就是不讓用戶收到HTTP-503的錯誤提示。
穩住用戶以后,接下去的工作是高效率地提供服務。高效率的服務,體現在Twitter業務流程6個步驟中的后4步。為什么Twitter對這4步這么有信心?
Reference,
[16] Updating Twitter without service disruptions.
(http://gojko.net/2009/03/16/qcon-london-2009-upgrading-twitter-without-service-disruptions/)
[30] Giants and Patriots draws 97.5 million US audience to the Super Bowl. (http://www.reuters.com/article/topNews/idUSN0420266320080204)
[31] Twitter traffic during Super Bowl 2008.
(http://blog.twitter.com/2008/02/highlights-from-superbowl-sunday.html)
[32] Joyent provides Twitter free extra capacity during the Super Bowl 2008.
(http://blog.twitter.com/2008/01/happy-happy-joyent.html)
[33] Twitter stopped using Joyent’s cloud at 10PM, Jan 30, 2008. (http://www.joyent.com/joyeurblog/2008/01/31/twitter-and-joyent-update/)
[34] The hasty divorce for Twitter and Joyent.
(http://www.datacenterknowledge.com/archives/2008/01/31/hasty-divorce-for-twitter-joyent/)
[35] The usage of Netcraft by Twitter.
(http://toolbar.netcraft.com/site_report?url=http://twitter.com)
【7】作為一種進步的不徹底
不徹底的工作方式,對于架構設計是一種進步。
當一個來自瀏覽器的用戶請求到達Twitter后臺系統的時候,第一個迎接它的,是Apache Web Server。第二個出場的,是Mongrel Rails Server。Mongrel既負責處理上傳的請求,也負責處理下載的請求。Mongrel處理上傳和下載的業務邏輯非常簡潔,但是簡潔的表象之下,卻蘊含著反常規的設計。這種反常規的設計,當然不是疏忽的結果,事實上,這正是Twitter架構中,最值得注意的亮點。
Figure 9. Twitter internal flows
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/fe8f_4095392354_66bd4bcc30_o.png
所謂上傳,是指用戶寫了一個新短信,上傳給Twitter以便發表。而下載,是指Twitter更新讀者的主頁,添加最新發表的短信。Twitter下載的方式,不是讀者主動發出請求的pull的方式,而是Twitter服務器主動把新內容push給讀者的方式。先看上傳,Mongrel處理上傳的邏輯很簡潔,分兩步。
1. 當Mongrel收到新短信后,分配一個新的短信ID。然后把新短信的ID,連同作者ID,緩存進Vector MemCached服務器。接著,把短信ID以及正文,緩存進Row MemCached服務器。這兩個緩存的內容,由Vector MemCached與Row MemCached在適當的時候,自動存放進MySQL數據庫中去。
2. Mongrel在Kestrel消息隊列服務器中,尋找每一個讀者及作者的消息隊列,如果沒有,就創建新的隊列。接著,Mongrel把新短信的ID,逐個放進“追”這位作者的所有在線讀者的隊列,以及作者本人的隊列。
品味一下這兩個步驟,感覺是Mongrel的工作不徹底。一,把短信及其相關IDs,緩存進Vector MemCached和Row Cached就萬事大吉,而不直接負責把這些內容存入MySQL數據庫。二,把短信ID扔進Kestrel消息隊列,就宣告上傳任務結束。Mongrel 沒有用任何方式去通知作者,他的短信已經被上傳。也不管讀者是否能讀到新發表的短信。
為什么Twitter采取了這種反常規的不徹底的工作方式?回答這個問題以前,不妨先看一看Mongrel處理下載的邏輯。把上傳與下載兩段邏輯聯系起來,對比一下,有助于理解。Mongrel下載的邏輯也很簡單,也分兩步。
1. 分別從作者和讀者的Kestrel消息隊列中,獲得新短信的ID。
2. 從Row MemCached緩存器那里獲得短信正文。以及從Page MemCached那里獲得讀者以及作者的主頁,更新這些主頁,也就是添加上新的短信的正文。然后通過Apache,push給讀者和作者。
對照Mongrel處理上傳和下載的兩段邏輯,不難發現每段邏輯都“不徹底”,合在一起才形成一個完整的流程。所謂不徹底的工作方式,反映了 Twitter架構設計的兩個“分”的理念。一,把一個完整的業務流程,分割成幾段相對獨立的工作,每一個工作由同一臺機器中不同的進程負責,甚至由不同的機器負責。二,把多個機器之間的協作,細化為數據與控制指令的傳遞,強調數據流與控制流的分離。
分割業務流程的做法,并不是Twitter的首創。事實上,三段論的架構,宗旨也是分割流程。Web Server負責HTTP的解析,Application Server負責業務邏輯,Database負責數據存儲。遵從這一宗旨,Application Server的業務邏輯也可以進一步分割。
1996年,發明TCL語言的前伯克利大學教授John Ousterhout,在Usenix大會上做了一個主題演講,題目是“為什么在多數情況下,多線程是一個糟糕的設計[36]”。2003年,同為伯克利大學教授的Eric Brewer及其學生們,發表了一篇題為“為什么對于高并發服務器來說,事件驅動是一個糟糕的設計[37]”。這兩個伯克利大學的同事,同室操戈,他們在爭論什么?
所謂多線程,簡單講就是由一根線程,從頭到尾地負責一個完整的業務流程。打個比方,就像修車行的師傅每個人負責修理一輛車。而所謂事件驅動,指的是把一個完整的業務流程,分割成幾個獨立工作,每個工作由一個或者幾個線程負責。打個比方,就像汽車制造廠里的流水線,有多個工位組成,每個工位由一位或者幾位工人負責。
很顯然,Twitter的做法,屬于事件驅動一派。事件驅動的好處在于動態調用資源。當某一個工作的負擔繁重,成為整個流程中的瓶頸的時候,事件驅動的架構可以很方便地調集更多資源,來化解壓力。對于單個機器而言,多線程和事件驅動的兩類設計,在性能方面的差異,并不是非常明顯。但是對于分布式系統而言,事件驅動的優勢發揮得更為淋漓盡致。
Twitter把業務流程做了兩次分割。一,分離了Mongrel與MySQL數據庫,Mongrel不直接插手MySQL數據庫的操作,而是委托MemCached全權負責。二,分離了上傳和下載兩段邏輯,兩段邏輯之間通過Kestrel隊列來傳遞控制指令。
在John Ousterhout和Eric Brewer兩位教授的爭論中,并沒有明確提出數據流與控制流分離的問題。所謂事件,既包括控制信號,也包括數據本身??紤]到通常數據的尺寸大,傳輸成本高,而控制信號的尺寸小,傳輸簡便。把數據流與控制流分離,可以進一步提高系統效率。
在Twitter系統中,Kestrel消息隊列專門用來傳輸控制信號,所謂控制信號,實際上就是IDs。而數據是短信正文,存放在Row MemCached中。誰去處理這則短信正文,由Kestrel去通知。
Twitter完成整個業務流程的平均時間是500ms,甚至能夠提高到200-300ms,說明在Twitter分布式系統中,事件驅動的設計是成功。
Kestrel消息隊列,是Twitter自行開發的。消息隊列的開源實現很多,Twitter為什么不用現成的免費工具,而去費神自己研發呢?
Reference,
[36] Why threads are a bad idea (for most purposes), 1996.
(http://www.stanford.edu/class/cs240/readings/threads-bad-usenix96.pdf)
[37] Why events are a bad idea (for high-concurrency servers), 2003.
(http://www.cs.berkeley.edu/~brewer/papers/threads-hotos-2003.pdf)
【8】 得過不且過
北京西直門立交橋的設計,經常遭人詬病??陀^上講,對于一座立交橋而言,能夠四通八達,就算得上基本完成任務了。大家詬病的原因,主要是因為行進路線太復雜。
當然,站在設計者角度講,他們需要綜合考慮來自各方面的制約。但是考慮到世界上立交橋比比皆是,各有各的難處,然而像西直門立交橋這樣讓人迷惑的,還真是少見。所以,對于西直門立交橋的設計者而言,困難是客觀存在的,但是改進的空間總還是有的。
Figure 10. 北京西直門立交橋行進路線
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/ef82_4113112287_86cfb1cffd_o.png
大型網站的架構設計也一樣,沿用傳統的設計,省心又省力,但是代價是網站的性能。網站的性能不好,用戶的體驗也不好。Twitter這樣的大型網站之所以能夠一飛沖天,不僅功能的設計迎合了時代的需要,同時,技術上精益求精也是成功的必要保障。
例如,從Mongrel到MemCached之間,需要一個數據傳輸通道?;蛘邍栏竦卣f,需要一個client library communicating to the memcached server。Twitter的工程師們,先用Ruby實現了一個通道。后來又用C實現了一個更快的通道。隨后,不斷地改進細節,不斷地提升數據傳輸的效率。這一系列的改進,使Twitter的運行速度,從原先不設緩存時,每秒鐘處理3.23個請求,到現在每秒處理139.03個請求,參見Figure 11。這個數據通道,現在定名為libmemcached,是開源項目 [38]。
Figure 11. Evolving from a Ruby memcached client to a C client with optimised hashing. These changes increases Twitter performance from 3.23 requests per second without caching, to 139.03 requests per second nowadays [14].
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/7666_4115077218_55c7250d43_o.png
又例如,Twitter系統中用消息隊列來傳遞控制信號。這些控制信號,從插入隊列,到被刪除,生命周期很短。短暫的生命周期,意味著消息隊列的垃圾回收(Garbage Collection)的效率,會嚴重影響整個系統的效率。因此,改進垃圾回收的機制,不斷提高效率,成為不可避免的問題。Twitter使用的消息隊列,原先不是Kestrel,而是用Ruby編寫的一個簡單的隊列工具。但是如果繼續沿用Ruby這種語言,性能優化的空間不大。Ruby的優點是集成了很多功能,從而大大減少了開發過程中編寫程序的工作量。但是優點也同時是缺點,集成的功能太多,拖累也就多,牽一發而動全身,造成優化困難。
Twitter工程師戲言,”Ruby抗拒優化”,(“Ruby is optimization resistant”, by Evan Weaver [14])。幾經嘗試以后,Twitter的工程師們最終放棄了Ruby語言,改用Scala語言,自行實現了一個隊列,命名為Kestrel [39]。
改換語言的主要動機是,Scala運行在JVM之上,因此優化Garbage Collection性能的手段豐富。Figure 12. 顯示了使用Kestrel以后,垃圾回收的滯后,在平時只有2ms,最高不超過4ms。高峰時段,平均滯后5ms,最高不超過35ms。
Figure 12. The latency of Twitter Kestrel garbage collection [14].
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/2f0a_4115072726_c611955bb2_o.png
RubyOnRails逐漸淡出Twitter,看來這是大勢所趨。最后一步,也是最高潮的一步,可能是替換Mongrel。事實上,Twitter所謂“API Server”,很可能是他們替換Mongrel的前奏。
Twitter的Evan Weaver說,“API Server”的運行效率,比Apache+Mongrel組合的速度快4倍。所謂Apache+Mongrel組合,是RubyOnRails的一種實現方式。Apache+Mongrel組合,每秒能夠處理139個請求,參見Figure 11,而“API Server” 每秒鐘能夠處理大約550個請求 [16]。換句話說,使用Apache+Mongrel組合,優點是降低了工程師們寫程序的負擔,但是代價是系統性能降低了4倍,換句話說,用戶平均等待的時間延長了4倍。
活著通常不難,活得精彩永遠很難。得過不且過,這是一種精神。
Reference,
[14] Improving running component of Twitter.
(http://qconlondon.com/london-2009/file?path=/qcon-london-2009/slides/EvanWeaver_ImprovingRunningComponentsAtTwitter.pdf)
[16] Updating Twitter without service disruptions.
(http://gojko.net/2009/03/16/qcon-london-2009-upgrading-twitter-without-service-disruptions/)
[38] Open source project, libmemcached, by Twitter.
(http://tangent.org/552/libmemcached.html)
[39] Open source project, Kestrel Messaging Queue, by Twitter.
(http://github.com/robey/kestrel)
【9】結語
這個系列討論了Twitter架構設計,尤其是cache的應用,數據流與控制流的組織等等獨特之處。把它們與抗洪搶險中,蓄洪,引流,渠道三種手段相對比,便于加深理解。同時參考實際運行的結果,驗證這樣的設計是否能夠應付實際運行中遇到的壓力。
解剖一個現實網站的架構,有一些難度。主要體現在相關資料散落各處,而且各個資料的視點不同,覆蓋面也不全。更嚴重的問題是,這些資料不是學術論文,質量良莠不齊,而且一些文章或多或少地存在缺失,甚至錯誤。
單純把這些資料羅列在一起,并不能滿足全景式的解剖的需要。整理這些資料的過程,很像是偵探辦案。福爾摩斯探案的方法,是證據加推理。
1. 如果觀察到證據O1,而造成O1出現的原因,有可能是R1,也有可能是R2或者R3。究竟哪一個原因,才是真正的原因,需要進一步收集更多的證據,例如O2,O3。如果造成O2 出現的可能的原因是R2和R4,造成O3 出現的可能原因是R3和R5。把所有證據O1 O2 O3,綜合起來考慮,可能性最大的原因必然是(R1,R2,R3), (R2,R4), (R3,R5) 的交集,也就是R2。這是反繹推理的過程。
2. 如果反繹推理仍然不能確定什么是最可能的原因,那么假定R2是真實的原因,采用演繹推理,R2必然導致O4證據的出現。接下去要做的事情是,確認O4是否真的出現,或者尋找O4肯定不會出現的證據。以此循環。
解剖網絡架構的方法,與探案很相似。只讀一篇資料是不夠的,需要多多收集資料,交叉印證。不僅交叉印證,而且引申印證,如果某一環節A是這樣設計的,那么關聯環節B必然相應地那樣設計。如果一時難以確定A到底是如何設計的,不妨先確定B是如何設計的。反推回來,就知道A應該如何設計了。
解剖網站架構,不僅有益,而且有趣。
Figure 13. Sherlock Holmes,福爾摩斯探案
Courtesy?http://alibuybuy-img1011.stor.sinaapp.com/2010/11/be50_c2053.jpg
【全文完】
本文來自:http://www.tektalk.org
原文鏈接:
- 解剖Twitter 【1】萬事開頭易
- 解剖Twitter 【2】三段論
- 解剖Twitter 【3】Cache == Cash
- 解剖Twitter 【4】抗洪需要隔離
- 解剖Twitter 【5】數據流與控制流
- 解剖Twitter【6】流量洪峰與云計算
- 解剖Twitter 【7】 作為一種進步的不徹底
- 解剖Twitter 【8】 得過不且過
- 解剖Twitter 【9】結語
- 目前還沒評論,等你發揮!