Tumblr:150億月瀏覽量背后的架構挑戰(上)
導讀:和許多新興的網站一樣,著名的輕博客服務 Tumblr 在急速發展中面臨了系統架構的瓶頸。每天 5 億次瀏覽量,峰值每秒 4 萬次請求,每天 3TB 新的數據存儲,超過 1000 臺服務器,這樣的情況下如何保證老系統平穩運行,平穩過渡到新的系統,Tumblr 正面臨巨大的挑戰。近日,HighScalability 網站的 Todd Hoff 采訪了該公司的分布式系統工程師 Blake Matheny,撰文系統介紹了網站的架構,內容很有價值。我們也非常希望國內的公司和團隊多做類似分享,貢獻于社區的同時,更能提升自身的江湖地位,對招聘、業務發展都好處多多。
英文原文:High Scalability
Tumblr 每月頁面瀏覽量超過 150 億次,已經成為火爆的博客社區。用戶也許喜歡它的簡約、美麗,對用戶體驗的強烈關注,或是友好而忙碌的溝通方式,總之,它深得人們的喜愛。
每月超過 30% 的增長當然不可能沒有挑戰,其中可靠性問題尤為艱巨。每天 5 億次瀏覽量,峰值每秒 4 萬次請求,每天 3TB 新的數據存儲,并運行于超過 1000 臺服務器上,所有這些幫助 Tumblr 實現巨大的經營規模。
創業公司邁向成功,都要邁過危險的迅速發展期這道門檻。尋找人才,不斷改造基礎架構,維護舊的架構,同時要面對逐月大增的流量,而且曾經只有 4 位工程師。這意味著必須艱難地選擇應該做什么,不該做什么。這就是 Tumblr 的狀況。好在現在已經有 20 位工程師了,可以有精力解決問題,并開發一些有意思的解決方案。
Tumblr 最開始是非常典型的 LAMP 應用。目前正在向分布式服務模型演進,該模型基于 Scala、HBase、Redis(著名開源K-V存儲方案)、Kafka(Apache 項目,出自 LinkedIn 的分布式發布-訂閱消息系統)、Finagle(由 推ter 開源的容錯、協議中立的 RPC 系統),此外還有一個有趣的基于 Cell 的架構,用來支持 Dashboard(CSDN 注:Tumblr 富有特色的用戶界面,類似于微博的時間軸)。
Tumblr 目前的最大問題是如何改造為一個大規模網站。系統架構正在從 LAMP 演進為最先進的技術組合,同時團隊也要從小的創業型發展為全副武裝、隨時待命的正規開發團隊,不斷創造出新的功能和基礎設施。下面就是 Blake Matheny 對 Tumblr 系統架構情況的介紹。
網站地址
主要數據
- 每天 5 億次 PV(頁面訪問量)
- 每月超過 150 億 PV
- 約 20 名工程師
- 峰值請求每秒近 4 萬次
- 每天超過 1TB 數據進入 Hadoop 集群
- MySQL/HBase/Redis/memcache 每天生成若干 TB 數據
- 每月增長 30%
- 近 1000 硬件節點用于生產環境
- 平均每位工程師每月負責數以億計的頁面訪問
- 每天上傳大約 50GB 的文章,每天跟帖更新數據大約2.7TB(CSDN 注:這兩個數據的比例看上去不太合理,據 Tumblr 數據科學家 Adam Laiacano 在 推ter 上解釋,前一個數據應該指的是文章的文本內容和元數據,不包括存儲在 S3 上的多媒體內容)
軟件環境
- 開發使用 OS X,生產環境使用 Linux(CentOS/Scientific)
- Apache
- PHP, Scala, Ruby
- Redis, HBase, MySQL
- Varnish, HAProxy, nginx
- memcache, Gearman(支持多語言的任務分發應用框架), Kafka, Kestrel(推ter 開源的分布式消息隊列系統), 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 是類似于 推ter 的時間軸,用戶由此可以看到自己關注的所有用戶的實時更新。與博客的擴展性不同,緩存作用不大,因為每次請求都不同,尤其是活躍的關注者。而且需要實時而 且一致,文章每天僅更新 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 的重要因素之一。這個來自 推ter 的庫可以解決大多數分布式問題,比如分布式跟蹤、服務發現、服務注冊等。
- 轉到 JVM 上之后,Finagle 提供了團隊所需的所有基本功能(Thrift, ZooKeeper 等),無需再開發許多網絡代碼,另外,團隊成員認識該項目的一些開發者。
- Foursquare 和 推ter 都在用 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 模型的,但是后來放棄了。對于運行后無需人工干預的工作,使用任務隊列。而且 推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 相當。
(先到這里吧,敬請期待下篇,包括如何用 Kafaka、Scribe、Thrift 實現內部活動流,Dashboard 的 Cell 架構,開發流程和經驗教訓等精彩內容。)
翻譯:包研,張志平,劉江;審校:劉江
來自: CSDN