Tumblr:150億月瀏覽量背後的架構挑戰

發表於2013-04-08

英文原文:High Scalability,編譯:CSDN

導讀:和許多新興的網站一樣,著名的輕部落格服務Tumblr在急速發展中面臨了系統架構的瓶頸。每天5億次瀏覽量,峰值每秒4萬次請求,每天3TB新的資料儲存,超過1000臺伺服器,這樣的情況下如何保證老系統平穩執行,平穩過渡到新的系統,Tumblr正面臨巨大的挑戰。近日,HighScalability網站的Todd Hoff採訪了該公司的分散式系統工程師Blake Matheny,撰文系統介紹了網站的架構,內容很有價值。

Tumblr每月頁面瀏覽量超過150億次,已經成為火爆的部落格社群。使用者也許喜歡它的簡約、美麗,對使用者體驗的強烈關注,或是友好而忙碌的溝通方式,總之,它深得人們的喜愛。

每月超過30%的增長當然不可能沒有挑戰,其中可靠性問題尤為艱鉅。每天5億次瀏覽量,峰值每秒4萬次請求,每天3TB新的資料儲存,並執行於超過1000臺伺服器上,所有這些幫助Tumblr實現巨大的經營規模。

創業公司邁向成功,都要邁過危險的迅速發展期這道門檻。尋找人才,不斷改造基礎架構,維護舊的架構,同時要面對逐月大增的流量,而且曾經只有4位工程師。這意味著必須艱難地選擇應該做什麼,不該做什麼。這就是Tumblr的狀況。好在現在已經有20位工程師了,可以有精力解決問題,並開發一些有意思的解決方案。

Tumblr最開始是非常典型的LAMP應用。目前正在向分散式服務模型演進,該模型基於ScalaHBaseRedis(著名開源K-V儲存方案)、Kafka(Apache專案,出自LinkedIn的分散式釋出-訂閱訊息系統)、Finagle(由Twitter開源的容錯、協議中立的RPC系統),此外還有一個有趣的基於Cell的架構,用來支援Dashboard(CSDN注:Tumblr富有特色的使用者介面,類似於微博的時間軸)。

Tumblr目前的最大問題是如何改造為一個大規模網站。系統架構正在從LAMP演進為最先進的技術組合,同時團隊也要從小的創業型發展為全副武裝、隨時待命的正規開發團隊,不斷創造出新的功能和基礎設施。下面就是Blake Matheny對Tumblr系統架構情況的介紹。

Tumblr:150億月瀏覽量背後的架構挑戰

網站地址

http://www.tumblr.com/

 

主要資料

▲每天5億次PV(頁面訪問量)

▲每月超過150億PV

▲約20名工程師

▲ 峰值請求每秒近4萬次

▲每天超過1TB資料進入Hadoop叢集

▲ MySQL/HBase/Redis/memcache每天生成若干TB資料

▲每月增長30%

▲近1000硬體節點用於生產環境

▲平均每位工程師每月負責數以億計的頁面訪問

▲每天上傳大約50GB的文章,每天跟帖更新資料大約2.7TB(CSDN注:這兩個資料的比例看上去不太合理,據Tumblr資料科學家Adam Laiacano在Twitter上解釋,前一個資料應該指的是文章的文字內容和後設資料,不包括儲存在S3上的多媒體內容)

 

軟體環境

▲開發使用OS X,生產環境使用Linux(CentOS/Scientific)

▲Apache

▲PHP, Scala, Ruby

▲Redis, HBase, MySQL

VarnishHAProxy, nginx

▲memcache, Gearman(支援多語言的任務分發應用框架), Kafka, Kestrel(Twitter開源的分散式訊息佇列系統), Finagle

▲ Thrift, HTTP

Func——一個安全、支援指令碼的遠端控制框架和API

▲ Git, Capistrano(多伺服器指令碼部署工具), Puppet, Jenkins

 

硬體環境

▲500臺Web伺服器

▲200臺資料庫伺服器(47 pool,20 shard)

▲30臺memcache伺服器

▲22臺Redis伺服器

▲15臺Varnish伺服器

▲25臺HAproxy節點

▲8臺nginx伺服器

▲14臺工作佇列伺服器(Kestrel + Gearman)

 

架構

 1. 相對其他社交網站而言,Tumblr有其獨特的使用模式:

▲每天有超過5千萬篇文章更新,平均每篇文章的跟帖又數以百計。使用者一般只有數百個粉絲。這與其他社會化網站裡少數使用者有幾百萬粉絲非常不同,使得Tumblr的擴充套件性極具挑戰性。

▲按使用者使用時間衡量,Tumblr已經是排名第二的社會化網站。內容的吸引力很強,有很多圖片和視訊,文章往往不短,一般也不會太長,但允許寫得很長。文章內容往往比較深入,使用者會花費更長的時間來閱讀。

▲使用者與其他使用者建立聯絡後,可能會在Dashboard上往回翻幾百頁逐篇閱讀,這與其他網站基本上只是部分資訊流不同。

▲使用者的數量龐大,使用者的平均到達範圍更廣,使用者較頻繁的發帖,這些都意味著有巨量的更新需要處理。

2. Tumblr目前執行在一個託管資料中心中,已在考慮地域上的分佈性。

3. Tumblr作為一個平臺,由兩個元件構成:公共Tumblelogs和Dashboard

▲公共Tumblelogs與部落格類似(此句請Tumblr使用者校正),並非動態,易於快取

▲Dashboard是類似於Twitter的時間軸,使用者由此可以看到自己關注的所有使用者的實時更新。與部落格的擴充套件性不同,快取作用不大,因為每次請求都不同,尤其是活躍的關注者。而且需要實時而且一致,文章每天僅更新50GB,跟帖每天更新2.7TB,所有的多媒體資料都儲存在S3上面。

▲大多數使用者以Tumblr作為內容瀏覽工具,每天瀏覽超過5億個頁面,70%的瀏覽來自Dashboard。

▲Dashboard的可用性已經不錯,但Tumblelog一直不夠好,因為基礎設施是老的,而且很難遷移。由於人手不足,一時半會兒還顧不上。

 

老的架構

Tumblr最開始是託管在Rackspace上的,每個自定義域名的部落格都有一個A記錄。當2007年Rackspace無法滿足其發展速度不得不遷移時,大量的使用者都需要同時遷移。所以他們不得不將自定義域名保留在Rackspace,然後再使用HAProxy和Varnish路由到新的資料中心。類似這樣的遺留問題很多。

開始的架構演進是典型的LAMP路線:

▲最初用PHP開發,幾乎所有程式設計師都用PHP

▲最初是三臺伺服器:一臺Web,一臺資料庫,一臺PHP

▲為了擴充套件,開始使用memcache,然後引入前端cache,然後在cache前再加HAProxy,然後是MySQL sharding(非常奏效)

▲採用“在單臺伺服器上榨出一切”的方式。過去一年已經用C開發了兩個後端服務:ID生成程式Staircar(用Redis支援Dashboard通知)

Dashboard採用了“擴散-收集”方式。當使用者訪問Dashboard時將顯示事件,來自所關注的使用者的事件是通過拉然後顯示的。這樣支撐了6個月。由於資料是按時間排序的,因此sharding模式不太管用。

 

新的架構

▲由於招人和開發速度等原因,改為以JVM為中心。目標是將一切從PHP應用改為服務,使應用變成請求鑑別、呈現等諸多服務之上的薄層。

▲這其中,非常重要的是選用了Scala和Finagle

▲在團隊內部有很多人具備Ruby和PHP經驗,所以Scala很有吸引力。

▲Finagle是選擇Scala的重要因素之一。這個來自Twitter的庫可以解決大多數分散式問題,比如分散式跟蹤、服務發現、服務註冊等。

▲轉到JVM上之後,Finagle提供了團隊所需的所有基本功能(Thrift, ZooKeeper等),無需再開發許多網路程式碼,另外,團隊成員認識該專案的一些開發者。

▲Foursquare和Twitter都在用Finagle,Meetup也在用Scala。

▲應用介面與Thrift類似,效能極佳。

▲團隊本來很喜歡Netty(Java非同步網路應用框架,2月4日剛剛釋出3.3.1最終版),但不想用Java,Scala是不錯的選擇。

▲選擇Finagle是因為它很酷,還認識幾個開發者。

之所以沒有選擇Node.js,是因為以JVM為基礎更容易擴充套件。Node的發展為時尚短,缺乏標準、最佳實踐以及大量久經測試的程式碼。而用Scala的話,可以使用所有Java程式碼。雖然其中並沒有多少可擴充套件的東西,也無法解決5毫秒響應時間、49秒HA、4萬每秒請求甚至有時每秒40萬次請求的問題。但是,Java的生態鏈要大得多,有很多資源可以利用。

內部服務從C/libevent為基礎正在轉向Scala/Finagle為基礎。

開始採用新的NoSQL儲存方案如HBase和Redis。但大量資料仍然儲存在大量分割槽的MySQL架構中,並沒有用HBase代替MySQL。HBase主要支援短地址生產程式(數以十億計)還有歷史資料和分析,非常結實。此外,HBase也用於高寫入需求場景,比如Dashboard重新整理時一秒上百萬的寫入。之所以還沒有替換HBase,是因為不能冒業務上風險,目前還是依靠人來負責更保險,先在一些小的、不那麼關鍵的專案中應用,以獲得經驗。MySQL和時間序列資料sharding(分片)的問題在於,總有一個分片太熱。另外,由於要在slave上插入併發,也會遇到讀複製延遲問題。

此外,還開發了一個公用服務框架

▲花了很多時間解決分散式系統管理這個運維問題。

▲為服務開發了一種Rails scaffolding,內部用模板來啟動服務。

▲所有服務從運維的角度來看都是一樣的,所有服務檢查統計資料、監控、啟動和停止的方式都一樣。

▲工具方面,構建過程圍繞SBT(一個Scala構建工具),使用外掛和輔助程式管理常見操作,包括在Git裡打標籤,釋出到程式碼庫等等。大多數程式設計師都不用再操心構建系統的細節了。

200臺資料庫伺服器中,很多是為了提高可用性而設,使用的是常規硬體,但MTBF(平均故障間隔時間)極低。故障時,備用充足。

為了支援PHP應用有6個後端服務,並有一個小組專門開發後端服務。新服務的釋出需要兩到三週,包括Dashboard通知、Dashboard二級索引、短地址生成、處理透明分片的memcache代理。其中在MySQL分片上耗時很多。雖然在紐約本地非常熱,但並沒有使用MongoDB,他們認為MySQL的可擴充套件性足夠了。

Gearman用於會長期執行無需人工干預的工作。

可用性是以達到範圍(reach)衡量的。使用者能夠訪問自定義域或者Dashboard嗎?也會用錯誤率。

歷史上總是解決那些最高優先順序的問題,而現在會對故障模式系統地分析和解決,目的是從使用者和應用的角度來定成功指標。(後一句原文似乎不全)

最開始Finagle是用於Actor模型的,但是後來放棄了。對於執行後無需人工干預的工作,使用任務佇列。而且Twitter的util工具庫中有Future實現,服務都是用Future(Scala中的無引數函式,在與函式關聯的並行操作沒有完成時,會阻塞呼叫方)實現的。當需要執行緒池的時候,就將Future傳入Future池。一切都提交到Future池進行非同步執行。

Scala提倡無共享狀態。由於已經在Twitter生產環境中經過測試,Finagle這方面應該是沒有問題的。使用Scala和Finagle中的結構需要避免可變狀態,不使用長期執行的狀態機。狀態從資料庫中拉出、使用再寫回資料庫。這樣做的好處是,開發人員不需要操心執行緒和鎖。

22臺Redis伺服器,每臺的都有8-32個例項,因此線上同時使用了100多個Redis例項。

▲Redis主要用於Dashboard通知的後端儲存。

▲所謂通知就是指某個使用者like了某篇文章這樣的事件。通知會在使用者的Dashboard中顯示,告訴他其他使用者對其內容做了哪些操作。

▲高寫入率使MySQL無法應對。

▲通知轉瞬即逝,所以即使遺漏也不會有嚴重問題,因此Redis是這一場景的合適選擇。

▲這也給了開發團隊瞭解Redis的機會。

▲使用中完全沒有發現Redis有任何問題,社群也非常棒。

▲開發了一個基於Scala Futures的Redis介面,該功能現在已經併入了Cell架構。

▲短地址生成程式使用Redis作為一級Cache,HBase作為永久儲存。

▲Dashboard的二級索引是以Redis為基礎開發的。

▲Redis還用作Gearman的持久儲存層,使用Finagle開發的memcache代理。

▲正在緩慢地從memcache轉向Redis。希望最終只用一個cache服務。效能上Redis與memcache相當。

 

內部的firehose(通訊管道)

▲內部的應用需要活躍的資訊流通道。這些資訊包括使用者建立/刪除的資訊,liking/unliking的提示,等等。挑戰在於這些資料要實時的分散式處理。我們希望能夠檢測內部執行狀況,應用的生態系統能夠可靠的生長,同時還需要建設分散式系統的控制中心。

▲以前,這些資訊是基於Scribe(Facebook開源的分散式日誌系統。)/Hadoop的分散式系統。服務會先記錄在Scribe中,並持續的長尾形式寫入,然後將資料輸送給應用。這種模式可以立即停止伸縮,尤其在峰值時每秒要建立數以千計的資訊。不要指望人們會細水長流式的釋出檔案和grep。

▲內部的firehose就像裝載著資訊的大巴,各種服務和應用通過Thrift與消防管線溝通。(一個可伸縮的跨語言的服務開發框架。)

▲LinkedIn的Kafka用於儲存資訊。內部人員通過HTTP連結firehose。經常面對巨大的資料衝擊,採用MySQL顯然不是一個好主意,分割槽實施越來越普遍。

▲firehose的模型是非常靈活的,而不像Twitter的firehose那樣資料被假定是丟失的。

▲firehose的資訊流可以及時的回放。他保留一週內的資料,可以調出這期間任何時間點的資料。

▲支援多個客戶端連線,而且不會看到重複的資料。每個客戶端有一個ID。Kafka支援客戶群,每個群中的客戶都用同一個ID,他們不會讀取重複的資料。可以建立多個客戶端使用同一個ID,而且不會看到重複的資料。這將保證資料的獨立性和並行處理。Kafka使用ZooKeeper(Apache推出的開源分散式應用程式協調服務。)定期檢查使用者閱讀了多少。

 

為Dashboard收件箱設計的Cell架構

▲現在支援Dashboard的功能的分散-集中架構非常受限,這種狀況不會持續很久。

▲解決方法是採用基於Cell架構的收件箱模型,與Facebook Messages非常相似。

▲收件箱與分散-集中架構是對立的。每一位使用者的dashboard都是由其追隨者的發言和行動組成的,並按照時間順序儲存。

▲就因為是收件箱就解決了分散-集中的問題。你可以會問到底在收件箱中放了些什麼,讓其如此廉價。這種方式將執行很長時間。

▲重寫Dashboard非常困難。資料已經分佈,但是使用者區域性升級產生的資料交換的質量還沒有完全搞定。

▲資料量是非常驚人的。平均每條訊息轉發給上百個不同的使用者,這比Facebook面對的困難還要大。大資料+高分佈率+多個資料中心。

▲每秒鐘上百萬次寫入,5萬次讀取。沒有重複和壓縮的資料增長為2.7TB,每秒百萬次寫入操作來自24位元組行鍵。

▲已經流行的應用按此方法執行。

▲cell

▲每個cell是獨立的,並儲存著一定數量使用者的全部資料。在使用者的Dashboard中顯示的所有資料也在這個cell中。

▲使用者對映到cell。一個資料中心有很多cell。

▲每個cell都有一個HBase的叢集,服務叢集,Redis的快取叢集。

▲使用者歸屬到cell,所有cell的共同為使用者發言提供支援。

▲每個cell都基於Finagle(Twitter推出的非同步的遠端過程呼叫庫),建設在HBase上,Thrift用於開發與firehose和各種請求與資料庫的連結。(請糾錯)

▲一個使用者進入Dashboard,其追隨者歸屬到特定的cell,這個服務節點通過HBase讀取他們的dashboard並返回資料。

▲後臺將追隨者的dashboard歸入當前使用者的table,並處理請求。

▲Redis的快取層用於cell內部處理使用者發言。

▲請求流:使用者釋出訊息,訊息將被寫入firehose,所有的cell處理這條訊息並把發言文字寫入資料庫,cell查詢是否所有釋出訊息追隨者都在本cell內,如果是的話,所有追隨者的收件箱將更新使用者的ID。(請糾錯

▲cell設計的優點:

▲大規模的請求被並行處理,元件相互隔離不會產生干擾。 cell是一個並行的單位,因此可以任意調整規格以適應使用者群的增長。

▲cell的故障是獨立的。一個Cell的故障不會影響其他cell。

▲cell的表現非常好,能夠進行各種升級測試,實施滾動升級,並測試不同版本的軟體。

▲關鍵的思想是容易遺漏的:所有的發言都是可以複製到所有的cell。

▲每個cell中儲存的所有發言的單一副本。 每個cell可以完全滿足Dashboard呈現請求。應用不用請求所有發言者的ID,只需要請求那些使用者的ID。(“那些使用者”所指不清,請指正。)他可以在dashboard返回內容。每一個cell都可以滿足Dashboard的所有需求,而不需要與其他cell進行通訊。

▲用到兩個HBase table :一個table用於儲存每個發言的副本,這個table相對較小。在cell內,這些資料將與儲存每一個發言者ID。第二個table告訴我們使用者的dashboard不需要顯示所有的追隨者。當使用者通過不同的終端訪問一個發言,並不代表閱讀了兩次。收件箱模型可以保證你閱讀到。

▲發言並不會直接進入到收件箱,因為那實在太大了。所以,發言者的ID將被髮送到收件箱,同時發言內容將進入cell。這個模式有效的減少了儲存需求,只需要返回使用者在收件箱中瀏覽發言的時間。而缺點是每一個cell儲存所有的發言副本。令人驚奇的是,所有發言比收件箱中的映象要小。(請糾錯)每天每個cell的發言增長50GB,收件箱每天增長2.7TB。使用者消耗的資源遠遠超過他們製造的。

▲使用者的dashboard不包含發言的內容,只顯示發言者的ID,主要的增長來自ID。(請Tumblr使用者糾錯)

▲當追隨者改變時,這種設計方案也是安全的。因為所有的發言都儲存在cell中了。如果只有追隨者的發言儲存在cell中,那麼當追隨者改變了,將需要一些回填工作。

▲另外一種設計方案是採用獨立的發言儲存叢集。這種設計的缺點是,如果群集出現故障,它會影響整個網站。因此,使用cell的設計以及後複製到所有cell的方式,建立了一個非常強大的架構。

▲一個使用者擁有上百萬的追隨者,這帶來非常大的困難,有選擇的處理使用者的追隨者以及他們的存取模式(見Feeding Frenzy

▲不同的使用者採用不同並且恰當的存取模式和分佈模型,兩個不同的分佈模式包括:一個適合受歡迎的使用者,一個使用大眾。

▲依據使用者的型別採用不同的資料處理方式,活躍使用者的發言並不會被真正釋出,發言將被有選擇的體現。(果真如此?請Tumblr使用者糾錯)

▲追隨了上百萬使用者的使用者,將像擁有上百萬追隨者的使用者那樣對待。

▲cell的大小非常難於決定。cell的大小直接影響網站的成敗。每個cell歸於的使用者數量是影響力之一。需要權衡接受怎樣的使用者體驗,以及為之付出多少投資。

▲從firehose中讀取資料將是對網路最大的考驗。在cell內部網路流量是可管理的。

▲當更多cell被增添到網路中來,他們可以進入到cell組中,並從firehose中讀取資料。一個分層的資料複製計劃。這可以幫助遷移到多個資料中心。

 

在紐約啟動運作

紐約具有獨特的環境,資金和廣告充足。招聘極具挑戰性,因為缺乏創業經驗。

在過去的幾年裡,紐約一直致力於推動創業。紐約大學和哥倫比亞大學有一些專案,鼓勵學生到初創企業實習,而不僅僅去華爾街。市長建立了一所學院,側重於技術。

 

團隊架構

▲團隊:基礎架構,平臺,SRE,產品,web ops,服務;

▲基礎架構:5層以下,IP地址和DNS,硬體配置;

▲平臺:核心應用開發,SQL分片,服務,Web運營;

▲SRE:在平臺和產品之間,側重於解決可靠性和擴充套件性的燃眉之急;

▲服務團隊:相對而言更具戰略性,

▲Web ops:負責問題檢測、響應和優化。

 

軟體部署

▲開發了一套rsync指令碼,可以隨處部署PHP應用程式。一旦機器的數量超過200臺,系統便開始出現問題,部署花費了很長時間才完成,機器處於部署程式中的各種狀態。

▲接下來,使用Capistrano(一個開源工具,可以在多臺伺服器上執行指令碼)在服務堆疊中構建部署程式(開發、分期、生產)。在幾十臺機器上部署可以正常工作,但當通過SSH部署到數百臺伺服器時,再次失敗。

▲現在,所有的機器上執行一個協調軟體。基於Redhat Func(一個安全的、指令碼化的遠端控制框架和介面)功能,一個輕量級的API用於向主機傳送命令,以構建擴充套件性。

▲建立部署是在Func的基礎上向主機傳送命令,避免了使用SSH。比如,想在組A上部署軟體,控制主機就可以找出隸屬於組A的節點,並執行部署命令。

▲部署命令通過Capistrano實施。

▲Func API可用於返回狀態報告,報告哪些機器上有這些軟體版本。

▲安全重啟任何服務,因為它們會關閉連線,然後重啟。

▲在啟用前的黑暗模式下執行所有功能。

 

展望

▲從哲學上將,任何人都可以使用自己想要的任意工具。但隨著團隊的發展壯大,這些工具出現了問題。新員工想要更好地融入團隊,快速地解決問題,必須以他們為中心,建立操作的標準化。

▲過程類似於Scrum(一種敏捷管理框架),非常敏捷。

▲每個開發人員都有一臺預配置的開發機器,並按照控制更新。

▲開發機會出現變化,測試,分期,乃至用於生產。

▲開發者使用VIM和TextMate。

▲測試是對PHP程式進行程式碼稽核。

▲在服務方面,他們已經實現了一個與提交相掛鉤的測試基礎架構,接下來將繼承並內建通知機制。

 

招聘流程

▲面試通常避免數學、猜謎、腦筋急轉彎等問題,而著重關注應聘者在工作中實際要做什麼。

▲著重程式設計技能。

▲面試不是比較,只是要找對的人。

▲挑戰在於找到具有可用性、擴充套件性經驗的人才,以應對Tumblr面臨的網路擁塞。

▲在Tumblr工程部落格(Tumblr Engineering Blog),他們對已過世的Dennis Ritchie和John McCarthy予以紀念。

 

經驗及教訓

▲自動化無處不在

▲MySQL(增加分片)規模,應用程式暫時還不行

▲Redis總能帶給人驚喜

▲基於Scala語言的應用執行效率是出色的

▲廢棄專案——當你不確定將如何工作時

▲不顧用在他們發展經歷中沒經歷過技術挑戰的人,聘用有技術實力的人是因為他們能適合你的團隊以 及工作。

▲選擇正確的軟體集合將會幫助你找到你需要的人

▲建立團隊的技能

▲閱讀文件和部落格文章。

▲多與同行交流,可以接觸一些領域中經驗豐富的人,例如與在Facebook、Twitter、LinkedIn的工程師 多交流,從他們身上可以學到很多

▲對技術要循序漸進,在正式投入使用之前他們煞費苦心的學習HBase和Redis。同時在試點專案中使用 或將其控制在有限損害範圍之內。

 

翻譯:包研,張志平,劉江;審校:劉江

相關文章