Tumblr 架構設計

jopen 11年前發布 | 78K 次閱讀 Tumblr 軟件架構

Tumblr最開始是非常典型的LAMP應用。目前正在向分布式服務模型演進,該模型基于ScalaHBaseRedisKafkaFinagle,此外還有一個有趣的基于Cell的架構,用于支持Dashboard .現在的重點被放在了解決他們PHP程序中的短期問題,找出問題,并正確的使用服務化去解決他們.

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

Tumblr網址:  http://www.tumblr.com/

統計

  • 每天 5 億頁面瀏覽量
  • 每月超過 150億 頁面瀏覽量
  • 約 20 名工程師
  • 峰值每秒大約 4 萬請求
  • 每天有超過 1TB 的數據進入 Hadoop 集群
  • 每天有幾個 TB 的數據進入 MySQL/HBase/Redis/Memcache
  • 每月增長 30%
  • 產品環境有大約 1000 個硬件節點
  • 平均每個工程師每月處理10億頁面訪問
  • 每天大約 50GB 的文章,關注者列表更新大約每天 2.7TB
  • Dashboard 每秒百萬次寫,每秒 5w 讀

軟件

  • OS X 用于開發,產品環境用 Linux (CentOS, Scientific)
  • Apache
  • PHP, Scala, Ruby
  • Redis, HBase, MySQL
  • Varnish, HA-Proxy, nginx,
  • Memcache, Gearman, Kafka, Kestrel, Finagle
  • Thrift, HTTP
  • Func - 一個安全的、腳本化的遠程控制框架和 API
  • Git, Capistrano, Puppet, Jenkins

硬件

  • 500 臺 Web 服務器
  • 200 數據庫服務器 (大多數是備用池)
    • 47 個池
    • 30 個分區
  • 30 臺 memcache 服務器
  • 22 臺 redis 服務器
  • 15 臺 varnish 服務器
  • 25 個 haproxy 節點
  • 8 個 nginx
  • 14 個作業隊列服務器 (kestrel + gearman)


構架

  • 與其他社交網站不同的是,Tumblr有其獨特的使用模式。
    • 每天有超過5千萬篇文章更新,平均每篇文章的跟帖又數以百計。用戶一般只有數百個粉絲。這與其他社會化網站里少數用戶有幾百萬粉絲非常不同,使得Tumblr的擴展性極具挑戰性。
    • 按用戶使用時間衡量,Tumblr已經是排名第二的社會化網站。內容的吸引力很強,有很多圖片和視頻,文章往往不短,一般也不會太長,但允許寫得很長。文章內容往往比較深入,用戶會花費更長的時間來閱讀。
    • 用戶與其他用戶建立聯系后,可能會在Dashboard上往回翻幾百頁逐篇閱讀,這與其他網站基本上只是部分信息流不同。
    • 用戶的數量龐大,用戶的平均到達范圍更廣,用戶較頻繁的發帖,這些都意味著有巨量的更新需要處理。
  • Tumblr目前運行在一個托管數據中心中,已在考慮地域上的分布式構架。
  • Tumblr平臺由兩個組件構成:公共Tumblelogs和Dashboard
    • 公共Tumblelogs與博客類似,并非動態,易于緩存
    • Dashboard是類似于推ter的時間軸,用戶由此可以看到自己關注的所有用戶的實時更新。
      • 與博客的擴展性不同,緩存作用不大,因為每次請求都不同,尤其是活躍的關注者。
      • 而且需要實時而且一致,文章每天僅更新50GB,跟帖每天更新2.7TB,所有的多媒體數據都存儲在S3上面。
    • 大多數用戶以Tumblr作為內容瀏覽工具,每天瀏覽超過5億個頁面,70%的瀏覽來自Dashboard。
    • Dashboard的可用性已經不錯,但Tumblelog一直不夠好,因為基礎設施是老的,而且很難遷移。由于人手不足,一時半會兒還顧不上。

老的Tumblr構架

  • Tumblr最開始是托管在Rackspace上的,每個自定義域名的博客都有一個A記錄。當2007年Rackspace無法滿足其發展速度不得不遷移時,大量的用戶都需要同時遷移。所以他們不得不將自定義域名保留在Rackspace,然后再使用HAProxy和Varnish路由到新的數據中心。類似這樣的遺留問題很多。
  • 開始的架構演進是典型的LAMP路線
    • 最初用PHP開發,幾乎所有程序員都用PHP
    • 最初是三臺服務器:一臺Web,一臺數據庫,一臺PHP
    • 為了擴展,開始使用memcache,然后引入前端cache,然后在cache前再加HAProxy,然后是非常奏效的MySQL sharding
    • 采用“在單臺服務器上榨出一切”的方式。過去一年已經用C開發了兩個后端服務:ID generatorStaircar(用Redis進行Dashboard通知)
  • Dashboard采用了“擴散-收集”方式。當用戶訪問Dashboard時將顯示事件,來自所關注的用戶的事件是通過拉然后顯示的。這樣支撐了6個月。由于數據是按時間排序的,因此sharding模式不太管用。 

新的構架

  • 由于招人和開發速度等原因,改為JVM為中心
  • 目標是將一切從PHP應用改為服務,使應用變成請求鑒別、呈現等諸多服務之上的薄層。
  • 選擇了Scala 和 Finagle
    • 在團隊內部有很多人具備Ruby和PHP經驗,所以Scala很有吸引力。
    • Finagle是選擇Scala的重要因素之一。這個來自推ter的庫可以解決大多數分布式問題,比如分布式跟蹤、服務發現、服務注冊等。
    • 轉到JVM上之后,Finagle提供了團隊所需的所有基本功能(Thrift, ZooKeeper等),無需再開發許多網絡代碼,另外,團隊成員認識該項目的一些開發者。
    • Foursquare和推ter都在用Finagle,Meetup也在用Scala。
    • 應用接口與Thrift類似,性能極佳。
    • 團隊本來很喜歡Netty,但不想用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里打標簽,發布到代碼庫等等。大多數程序員都不用再操心構建系統的細節了。
  • 40臺服務器采用HAProxy進行負載均衡,Varnish進行緩存.
  • 200臺數據庫服務器中,很多是為了提高可用性而設,使用的是常規硬件,但MTBF(平均故障間隔時間)極低。故障時,備用充足。
  • 500臺服務器運行Apache和其他PHP程序。
  • 6個為了支持PHP應用的后端服務,并有一個小組專門開發后端服務。新服務的發布需要兩到三周,包括Dashboard通知、Dashboard二級索引、短地址生成、處理透明分片的memcache代理。
  • 其中在MySQL分片上耗時很多。雖然在紐約本地非常熱,但并沒有使用MongoDB,他們認為MySQL的可擴展性足夠了。
  • Gearman用于會長期運行無需人工干預的工作。
  • 可用性是以達到范圍(reach)衡量的。用戶能夠訪問自定義域或者Dashboard嗎?也會用錯誤率。
  • 歷史上總是解決那些最高優先級的問題,而現在會對故障模式系統地分析和解決,目的是從用戶和應用的角度來定成功指標。
  • 最開始Finagle是用于Actor模型的,但是后來放棄了。對于運行后無需人工干預的工作,使用任務隊列。而且推ter的util工具庫中有Future實現,服務都是用Future(Scala中的無參數函數,在與函數關聯的并行操作沒有完成時,會阻塞調用方)實現的。當需要線程池的時候,就將Future傳入Future池。一切都提交到Future池進行異步執行。
  • Scala提倡無共享狀態。由于已經在推ter生產環境中經過測試,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 (非死book 開源的分布式日志系統。)/Hadoop 的分布式系統。服務會先記錄在 Scribe 中,并持續的長尾形式寫入,然后將數據輸送給應用。這種模式可以立即停止伸縮,尤其在峰值時每秒要創建數以千計的信息。不要指望人們會細水長流式的發布文 件和 grep。
  • 內部的 firehose 就像裝載著信息的大巴,各種服務和應用通過 Thrift 與消防管線溝通。(一個可伸縮的跨語言的服務開發框架。)
  • LinkedIn 的 Kafka 用于存儲信息。內部人員通過 HTTP 鏈接 firehose。經常面對巨大的數據沖擊,采用 MySQL 顯然不是一個好主意,分區實施越來越普遍。
  • firehose 的模型是非常靈活的,而不像 推ter 的 firehose 那樣數據被假定是丟失的。
    • firehose 的信息流可以及時的回放。他保留一周內的數據,可以調出這期間任何時間點的數據。
    • 支持多個客戶端連接,而且不會看到重復的數據。每個客戶端有一個 ID。Kafka 支持客戶群,每個群中的客戶都用同一個 ID,他們不會讀取重復的數據。可以創建多個客戶端使用同一個 ID,而且不會看到重復的數據。這將保證數據的獨立性和并行處理。Kafka 使用 ZooKeeper (Apache 推出的開源分布式應用程序協調服務。)定期檢查用戶閱讀了多少。

為 Dashboard 收件箱設計的 Cell 架構

  • 現在支持 Dashboard 的功能的分散-集中架構非常受限,這種狀況不會持續很久。
    • 解決方法是采用基于 Cell 架構的收件箱模型,與 非死book Messages 非常相似。
    • 收件箱與分散-集中架構是對立的。每一位用戶的 dashboard 都是由其追隨者的發言和行動組成的,并按照時間順序存儲。
    • 就因為是收件箱就解決了分散-集中的問題。你可以會問到底在收件箱中放了些什么,讓其如此廉價。這種方式將運行很長時間。
  • 重寫 Dashboard 非常困難。數據已經分布,但是用戶局部升級產生的數據交換的質量還沒有完全搞定。
    • 數據量是非常驚人的。平均每條消息轉發給上百個不同的用戶,這比 非死book 面對的困難還要大。大數據+高分布率+多個數據中心。
    • 每秒鐘上百萬次寫入,5萬次讀取。沒有重復和壓縮的數據增長為2.7TB,每秒百萬次寫入操作來自 24 字節行鍵。
    • 已經流行的應用按此方法運行。
  • Cell構架
    • 每個 cell 是獨立的,并保存著一定數量用戶的全部數據。在用戶的 Dashboard 中顯示的所有數據也在這個 cell 中。
    • 用戶映射到 cell。一個數據中心有很多 cell。
    • 每個 cell 都有一個 HBase 的集群,服務集群,Redis 的緩存集群。
    • 用戶歸屬到 cell,所有 cell 的共同為用戶發言提供支持。
    • 每個 cell 都基于 Finagle(推ter 推出的異步的遠程過程調用庫),建設在 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。
    • 當追隨者改變時,這種設計方案也是安全的。因為所有的發言都保存在 cell 中了。如果只有追隨者的發言保存在 cell 中,那么當追隨者改變了,將需要一些回填工作。
    • 另外一種設計方案是采用獨立的發言存儲集群。這種設計的缺點是,如果群集出現故障,它會影響整個網站。因此,使用 cell 的設計以及后復制到所有 cell 的方式,創建了一個非常強大的架構。
  • 一個用戶擁有上百萬的追隨者,這帶來非常大的困難,有選擇的處理用戶的追隨者以及他們的存取模式(見Feeding Frenzy
    • 不同的用戶采用不同并且恰當的存取模式和分布模型,兩個不同的分布模式包括:一個適合受歡迎的用戶,一個使用大眾。
    • 依據用戶的類型采用不同的數據處理方式,活躍用戶的發言并不會被真正發布,發言將被有選擇的體現。
    • 追隨了上百萬用戶的用戶,將像擁有上百萬追隨者的用戶那樣對待。
  • 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的命令實現部署。它可以可以從git倉庫中拉取代碼。正因為是基于HTTP的,所以非常易于擴展。他們喜歡 Capistrano,因為它支持簡單的基于目錄的版本控制,能夠很好地管理PHP程序。版本更新的時候,每個目錄都包含一個SHA,所以很容易檢查版本的正確性。
  • Func API 可用于返回狀態報告,報告哪些機器上有這些軟件版本。
  • 安全重啟任何服務,因為它們會關閉連接,然后重啟。
  • 在激活前的黑暗模式下運行所有功能。

開發

  • 從哲學上,任何人都可以使用自己想要的任意工具。但隨著團隊的發展壯大,這些工具出現了問題。新員工想要更好地融入團隊,快速地解決問題,必須以他們為中心,建立操作的標準化。
  • 過程類似于 Scrum(一種敏捷管理框架),非常敏捷。
  • 每個開發人員都有一臺預配置的開發機器,并按照控制更新。
  • 開發機會出現變化,測試,分期,乃至用于生產。
  • 開發者使用 VIM 和 TextMate。
  • 測試是對 PHP 程序進行代碼審核。
  • 在服務方面,他們已經實現了一個與提交相掛鉤的測試基礎架構,接下來將繼承并內建通知機制。

招聘流程

  • 面試通常避免數學、猜謎、腦筋急轉彎等問題,而著重關注應聘者在工作中實際要做什么。
  • 著重編程技能。
  • 面試不是比較,只是要找對的人。
  • 挑戰在于找到具有可用性、擴展性經驗的人才,以應對 Tumblr 面臨的網絡擁塞。
    • 例如,對于一個新的ID生成器器,他們需要一個高可用的JVM進程,并且需要在1萬次每分鐘的請求下及最多使用500兆內存的情況下完成1毫秒以下的響應。他們發現串行收集器給這個特定的工作負載帶來最低的延遲。在 JVM優化上花了大量的時間。
  • 在 Tumblr 工程博客(Tumblr Engineering Blog),他們對已過世的 Dennis Ritchie 和 John McCarthy 予以紀念。

經驗及教訓

  • 自動化無處不在
  • MySQL(增加分片)規模,應用程序暫時還不行
  • Redis 總能帶給人驚喜
  • 基于 Scala 語言的應用執行效率是出色的
  • 廢棄項目——當你不確定將如何工作時
  • 不顧用在他們發展經歷中沒經歷過技術挑戰的人,聘用有技術實力的人是因為他們能適合你的團隊以及工作。
  • 選擇正確的軟件集合將會幫助你找到你需要的人
  • 建立團隊的技能
  • 閱讀文檔和博客文章。
  • 多與同行交流,可以接觸一些領域中經驗豐富的人,例如與在 非死book、推ter、LinkedIn 的工程師多交流,從他們身上可以學到很多
  • 對技術要循序漸進,在正式投入使用之前他們煞費苦心的學習 HBase 和 Redis。同時在試點項目中使用或將其控制在有限損害范圍之內。
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!