從Twitter的架構(gòu)變遷看Web2.0的架構(gòu)技術(shù)
51CTO推薦:視頻專題-大型網(wǎng)站架構(gòu)技術(shù)專家談
Evan Weaver是Twitter服務(wù)團(tuán)隊(duì)的總工程師,他的主要工作是優(yōu)化與伸縮性。在一個(gè)技術(shù)峰會(huì)上,他談到了Twitter的架構(gòu),特別是在過(guò)去一年當(dāng)中為提升Web站點(diǎn)性能所執(zhí)行的優(yōu)化。
Twitter使用的大部分工具都是開(kāi)源的。其結(jié)構(gòu)是用Rails作前端,C,Scala和Java組成中間的業(yè)務(wù)層,使用MySQL存儲(chǔ)數(shù)據(jù)。所有的東西都保存在RAM里,而數(shù)據(jù)庫(kù)只是用作備份。Rails前端處理展現(xiàn),緩存組織,DB查詢以及同步插入。這一前端主要由幾部分客戶服務(wù)粘合而成,大部分是C寫(xiě)的:MySQL客戶端,Memcached客戶端,一個(gè)JSON端,以及其它。
中間件使用了Memcached,Varnish用于頁(yè)面緩存,一個(gè)用Scala寫(xiě)成的MQ,Kestrel和一個(gè)Comet服務(wù)器也正在規(guī)劃之中,該服務(wù)器也是用Scala寫(xiě)成,當(dāng)客戶端想要跟蹤大量的tweet時(shí)它就能派上用場(chǎng)。
Twitter是作為一個(gè)“內(nèi)容管理平臺(tái)而非消息管理平臺(tái)”開(kāi)始的,因此從一開(kāi)始基于聚合讀取的模型改變到現(xiàn)在的所有用戶都需要更新最新tweet的消息模型,需要許許多多的優(yōu)化。這一改動(dòng)主要在于三個(gè)方面:緩存,MQ以及Memcached客戶端。
緩存
每個(gè)tweet平均被126個(gè)用戶跟蹤,所以這里有著明顯的緩存需求。在最初的配置中,只有API有著一個(gè),當(dāng)每次從一個(gè)用戶那里來(lái)了一個(gè)tweet時(shí)就會(huì)失效,而應(yīng)用的其它部分都是無(wú)緩存的:
第一個(gè)架構(gòu)改動(dòng)是創(chuàng)建一個(gè)直寫(xiě)式向量緩存包含了一個(gè)tweet ID的數(shù)組,tweet ID是序列化的64位整數(shù)。這一緩存的命中率是99%。
第二個(gè)架構(gòu)改動(dòng)是加入另一個(gè)直寫(xiě)式行緩存,它包含了數(shù)據(jù)庫(kù)記錄:用戶和tweets。這一緩存有著95%的命中率并且使用了Nick Kallen的名為Cache Money的Rails插件。Nick是Twitter的一名系統(tǒng)架構(gòu)師。
第三個(gè)架構(gòu)改動(dòng)是引入了一個(gè)直讀式的碎片緩存,它包含了通過(guò)API客戶端訪問(wèn)到的tweets的序列化版本,這些tweets可以被打包成JSON,XML或者是Atom的格式,有著同樣是95%的命中率。這一碎片緩存“直接消費(fèi)向量,而且如果現(xiàn)在緩存了一個(gè)序列化的碎片,它不會(huì)加載你試圖看到的該tweet的實(shí)際的行,因此它將在大量時(shí)間將數(shù)據(jù)庫(kù)置于短路狀態(tài),”Evan這樣說(shuō)到。
還有另一個(gè)改動(dòng)是為頁(yè)面緩存創(chuàng)建一個(gè)單獨(dú)的緩存池。根據(jù)Evan的說(shuō)法,該頁(yè)面緩存池使用了一個(gè)分代的鍵模式,而不是直接的失效,因?yàn)橛脩艨梢园l(fā)送HTTP的if-modified-since并且將任何他們想要的時(shí)間戳放入請(qǐng)求路徑,我們需要將這一數(shù)組切片并只呈現(xiàn)給他們他們想要看到的tweets,但我們不想跟蹤客戶端所使用的所有可能的鍵值。這一分代的鍵模式有一個(gè)大問(wèn)題,在于它不會(huì)刪除所有失效的鍵值。每一個(gè)被加入的對(duì)應(yīng)到人們所接收的tweets數(shù)目的頁(yè)面都會(huì)向緩存推送有效的數(shù)據(jù),最后變得我們的緩存僅僅只有五個(gè)小時(shí)的有效生命周期,因?yàn)樗械捻?yè)面緩存都將流過(guò)。
當(dāng)該頁(yè)面緩存轉(zhuǎn)移到其自己的池之后,緩存未命中降低了將近50%。
這是Twitter現(xiàn)在所使用的緩存模式:
因?yàn)?0%的Twitter流量都來(lái)自API,因此還有額外的二層緩存,每一個(gè)最多將處理95%來(lái)自前一層的請(qǐng)求。整體的緩存改動(dòng)總共有百分之二三十的優(yōu)化,它帶來(lái)了
10倍的容量提升,它本可以更多,但現(xiàn)在我們遇到了另一瓶頸...我們的策略是首先加入直讀式緩存,確保它正確失效,然后再轉(zhuǎn)移到直寫(xiě)式緩存并且在線修復(fù),而不是當(dāng)一個(gè)新的tweet ID進(jìn)來(lái)時(shí)每次都要銷毀。
消息隊(duì)列
因?yàn)?,平均?lái)說(shuō)一個(gè)用戶有126個(gè)追隨者,這就意味著每個(gè)tweet將有126個(gè)消息在隊(duì)列里。同時(shí),流量會(huì)有出現(xiàn)高峰的時(shí)候,就像在奧巴馬就職的時(shí)候達(dá)到了每秒幾百個(gè)tweet或者說(shuō)是成千上萬(wàn)的消息在隊(duì)列里,是正常流量的3倍。MQ應(yīng)當(dāng)去化解這一高峰并隨著時(shí)間將其分散,這樣就不用增加許多額外的硬件。Twitter的MQ很簡(jiǎn)單:基于Memcached的協(xié)議,job之間是無(wú)序的,服務(wù)器之間沒(méi)有共享的狀態(tài),所有的東西都保存在RAM里,并且是事務(wù)性的。
第一版的MQ實(shí)現(xiàn)是用的Starling,以Ruby寫(xiě)成,伸縮性不佳,特別是Ruby的GC不是分代的。這將導(dǎo)致MQ在某一點(diǎn)上崩潰,因?yàn)镚C完成工作時(shí)將會(huì)把整個(gè)隊(duì)列處理中止。因此作出了將MQ移植到Scala上的決定,它有著更為成熟的JVM GC機(jī)制?,F(xiàn)有的MQ僅僅只有1200行代碼并且運(yùn)行在3臺(tái)服務(wù)器上。
Memcached客戶端
Memcached客戶端的優(yōu)化目的是試圖優(yōu)化集群負(fù)載?,F(xiàn)在的客戶端用的是libmemcached,Twitter是其最重要的用戶和其代碼庫(kù)最重要的貢獻(xiàn)者?;诖?,持續(xù)一年的碎片緩存優(yōu)化帶來(lái)了50倍的每秒頁(yè)面請(qǐng)求服務(wù)增加。
因?yàn)檎?qǐng)求來(lái)自的位置難以確定,處理請(qǐng)求最快的辦法就是將預(yù)先計(jì)算好的數(shù)據(jù)存儲(chǔ)在網(wǎng)絡(luò)RAM上,而不是當(dāng)需要的時(shí)候在每個(gè)服務(wù)器上都重新計(jì)算一次。這一方式被主流的Web 2.0站點(diǎn)所使用,它們幾乎都是完全直接運(yùn)行于內(nèi)存之上。根據(jù)Evan的說(shuō)法,下一步就是“既可伸縮的讀持續(xù)了一年之后,(解決)可伸縮的寫(xiě),然后就是多協(xié)同定位的問(wèn)題”。
【編輯推薦】