Pinterest架構:兩年內月PV從零到百億

發表於2013-04-20

英文原文:Scaling Pinterest – From 0 to 10s of Billions of Page Views a Month in Two Years 編譯:oschina

Pinterest正經歷了指數級曲線般的增長,每隔一個半月就翻番。在這兩年裡,Pinterest,從 每月PV量0增長到100億,從兩名c創始人和一個工程師成長為四十個工程師,從一臺MySQL 伺服器增長到180臺Web 伺服器(Web Engine),240臺介面伺服器(API Engine), 88臺MySQL 資料庫 (cc2.8xlarge) ,並且每臺DB有一個備份伺服器,110臺Redis 例項服務(Redis Instance),200臺 Memcache 例項服務(Memcache  Instance)。

令人歎為觀止的增長。想一探Pinterest的傳奇嗎?我們請來了Pinterest的兩位創始人Yashwanth Nelapati 和 Marty Weiner,他們將以 Scaling Pinterest為題講述關於Pinterest架構的充滿戲劇化的傳奇故事。他們說如果能在一年半前飛速發展時能看到有人做類似題材的演講的話,他們就會有更多的選擇,以避免自己在這一年半里做出的很多錯誤的決定。

這是一個很不錯的演講,充滿了令人驚訝的細節。同時這個演講也是很務實的,歸根結底,它帶來了可讓大家選擇的策略。極度推薦

這篇演講中有兩個我最為看重的經驗:

1.強大的架構在處理增長時通過簡單增加相同的東西(伺服器)來應對,同時還能保證系統的正確性。當遇到某種(效能)問題時,你想通過砸錢來擴容指的是你可以簡單增加伺服器(boxes)。如果你的架構能夠做到這一點,那它就如金子一般強大而珍貴!

2. 當某些(效能問題)快到極限時大多數技術都會以他們自己的方式失敗。這導致他們在稽核工具時要考慮以下一些特性:成熟,好且簡單,有名氣且用的人多,良好的支援,持續的優異效能,很少失敗,開源。按照這樣的標準,他們選擇了:MySQL, Solr, Memcache, and Redis,放棄了Cassandra ,Mongo。

這兩點經驗是相互聯絡的。遵循(2)中提到的標準的工具可以在擴容時簡單增加伺服器(boxes).當負載增加了,成熟的產品更少會有問題。當你遇到問題時,你至少希望它的社群團隊能夠幫助解決。當你使用的工具過於技巧化和過於講究時,你會發現你遇到一堵無法逾越的牆。

在這段演講裡,碎片化(sharding)優於叢集(clusterting)的觀點是我認為最好的一部分。為了應對增長,通過增加資源,更少失敗的模式,成熟,簡單,良好的支援,最終圓滿完成。請注意他們選擇的工具以sharding的方式增長,而不是clustering。關於他們為什麼選擇sharding和他們如何做sharding是很有趣的事,這很可能觸及到你以前未考慮過的場景。

現在,讓我們看看Pinterest如何擴容:

基本概念

  • Pins是一幅關於其他資訊的集合的圖片,描述了為什麼它對於使用者來說很重要,可以鏈回到他們發現它的地方。
  • Pinterest是一個社交網路。你可以追蹤人或者板報(boards).
  • Database:它包含了擁有pins的板報(boards)和擁有板報(boards)的人 ,可以追蹤或重新建立(repin)聯絡,還包含認證資訊。

啟動於2010年三月–自我發現時期

此時此刻,你甚至不知道你在做的這個產品將要做什麼。你有想法,迭代開發更新產品的頻率很高。最終因遇到一些在現實生活中永遠不會遇到的奇怪的簡短的MySQL查詢而結束。

早期的一些數字:

  • 兩個創始人
  • 一個工程師
  • Rackspace託管伺服器
  • 一個小型web引擎
  • 一個小型MySQL資料庫

2011年1月

扔在潛伏前進中,產品得到了一些使用者反饋。以下是資料:

  • Amazon EC2 + S3 + CloudFront雲服務
  • 一臺NGinX,4臺Web 引擎(作冗餘用,不是真正為了負載)
  • 一臺MySQL資料庫+一臺讀備份伺服器(防止主伺服器當機)
  • 一個任務佇列+兩個任務處理
  • 一臺MongoDB(為了計數)
  • 兩個工程師

至2011年9月–試執行階段

每一個半月翻翻的瘋狂增長階段。

  • 當高速發展時每個晚上每個星期都會有技術失敗的情況發生
  • 這時,你閱讀大量白皮書,它會告訴你把這個增加進來就行了。當他們新增了大量技術時,毫無例外都失敗了。
  • 最終你得到一個極為複雜的架構圖:
    • Amazon EC2 + S3 + CloudFront
    • 2NGinX, 16 Web Engines + 2 API Engines
    • 5 Functionally Sharged MySQL DB + 9 讀備份
    • 4 Cassandra 節點
    • 15 Membase 節點(分成三個單獨的叢集)
    • 8 Memcache 節點
    • 10 Redis 節點
    • 3 任務路由(Task Routers)+ 4 Task Processors
    • 4 ElasticSearch 節點
    • 3 Mongo叢集
    • 3名工程師
  • 5種主要的資料庫技術只為了應付他們自己的資料
  • 增長極快以至MySQL負載很高,而其他一些技術都快到達極限
  • 當你把某些技術的應用推至極限時,他們又以自己的方式宣告失敗。
  • 放棄一些技術並問它們到底能做什麼。對每一件事情重新構架,海量工作量。

架構成熟 2012 1月

重新設計的系統架構如下:

  • Amazon EC2 + S3 + Akamai, ELB
  • 90 Web Engines + 50 API Engines
  • 66 MySQL DBs (m1.xlarge) + 1 slave each
  • 59 Redis Instances
  • 51 Memcache Instances
  • 1 Redis Task Manager + 25 Task Processors
  • Sharded Solr
  • 6 Engineers .使用Mysql,Redis,Memcache Solr,他們的優勢是簡單高效並且是成熟的技術。 隨著Web流量增加,Iphone的流量也隨之開始越來越大。
    穩定期 2012 10月 12 僅僅在一月份以後,大概就有4倍的流量增長。 系統架構資料如下: The numbers now looks like:
    • Amazon EC2 + S3 + Edge Cast,Akamai, Level 3
    • 180 Web Engines + 240 API Engines
    • 88 MySQL DBs (cc2.8xlarge) + 1 slave each
    • 110 Redis Instances
    • 200 Memcache Instances
    • 4 Redis Task Manager + 80 Task Processors
    • Sharded Solr
    • 40 Engineers (and growing)

    注意到,此時的架構應該是合理的,只是通過增加更多的伺服器。你認為此時通過更多的投入來應對這麼大的規模的流量,投入更多的硬體來解決這個問題, 下一步 遷移到SSDs

為什麼是Amazon EC2/S3

  • 相當的可靠。資料中心也會當機, Multitenancy 加入了不少風險,但不是壞處。
  • 良好的彙報和支援。他們確實有很不錯的架構師而且他們知道問題在哪裡。
  • 良好的額外服務支援(peripherals),特別是當你的應用處於增長時期。你可能在App Engine中轉暈,你不用親自去實現,只需要簡單和他們的服務打交道,例如maged cache,負載均衡,對映和化簡,資料庫和其他所有方面。Amazon的服務特別適合起步階段,之後你可以招聘工程師來優化程式。
  • 分秒鐘獲得新的服務例項。這是雲服務的威力。特別是當你只有兩名工程師,你不用擔心容量規劃或者為了10臺memcache伺服器等上兩週。10臺memcache伺服器幾分鐘內就能加完。
  • 反對的理由:有限的選擇。直到最近你才能用SSD而且還沒高記憶體配置的方案。
  • 贊成的理由:還是有限的選擇。你不需要面對一大堆配置迥異的伺服器。

為什麼是 MySQL?

  • 非常成熟
  • 非常耐用。從不當機且不會丟失資料。
  • 招聘方便,一大堆工程師懂MySQL.
  • 反應時間和請求數量(requies rate,我認為是request rate參考下面)是線性增長的。有些資料庫技術的反應時間在請求飆升時不是很好。
  • 很好的軟體支援– XtraBackup, Innotop, Maatkit
  • 很好的社群,問的問題總能輕易獲取到答案
  • 很好的廠商支援,譬如Percona
  • 開源–這一點很重要,特別是你剛開始沒有很多資金支援時。

為什麼選擇Memcache?

  • 非常成熟
  • 非常簡單。它就是一個socket的雜湊表。
  • 效能一直很好
  • 很多人知道並喜歡
  • 從不崩潰
  • 免費

為什麼選擇Redis?

  • 還不成熟,但它是非常好並且相當簡單。
  • 提供了各種的資料結構。
  • 可以持久化和複製,並且可以選擇如何實現它們。你可以用MySQL風格持久化,或者你可以不要持久化,或者你只要3小時的持久化。
  • Redis上的資料只儲存3個小時,沒有3小時以上的複本。他們只保留3個小時的備份。
  • 如果儲存資料的裝置發生故障,而它們只是備份了幾個小時。這不是完全可靠的,但它很簡單。你並不需要複雜的持久化和複製。這是一個更簡單,更便宜的架構。
  • 很多人知道並喜歡
  • 效能一直很好
  • 很少的一些故障。你需要了解一些小故障,學習並解決它們,使它越來越成熟。
  • 免費

Solr

  • 只需要幾分鐘的安裝時間,就可以投入使用
  • 不能擴充套件到多於一臺的機器上(最新版本並非如此)
  • 嘗試彈性搜尋,但是以Pinterest的規模來說,可能會因為零碎檔案和查詢太多而產生問題。
  • 選擇使用Websolr,但是Pinterest擁有搜尋團隊,將來可能會開發自己的版本。

叢集vs.分片

  • 在迅速擴充套件的過程中,Pinterest認識到每次負載的增加,都需要均勻的傳播他們的資料。
  • 針對問題先確定解決方案的範圍,他們選擇的範圍是叢集和分片之間的一系列解決方案。

叢集 —— 所有事情都是自動化的

  • 示例: Cassandra, MemBase, HBase
  • 結論: 太可怕了,不是在現在,可能在將來,但現在太複雜了,有非常多的故障點
  • 屬性:
    • 自動化資料分佈
    • 可移動資料
    • 可重新進行分佈均衡
    • 節點間可通訊,大量的握手、對話
  • 有點:
    • 自動伸縮資料儲存,至少白皮書上是這麼說的
    • 安裝簡單
    • 在空間中分佈儲存你的資料,可在不同區域有資料中心
    • 高可用性
    • 負載均衡
    • 沒有單點故障
  • 缺點 (來自使用者一手的體驗):
    • 還是相當年輕不成熟
    • 還是太複雜,一大堆節點必須對稱的協議,這是一個在生產環境中難以解決的問題。
    • 很少的社群支援,有一個沿著不同產品線的分裂社群會減少每個陣營的支援。
    • 很少工程師有相關的知識,可能是很多工程師都沒用過 Cassandra.
    • 複雜和和可怕的升級機制
    • 叢集管理演算法是一個 SPOF 單點故障,如果有個 bug 影響每個節點,這可能會當機 4 次。
    • 叢集管理器編碼複雜,有如下一些失敗的模式:
      • 資料重新均衡中斷:當一個新機器加入然後資料開始複製,它被卡住了。你做什麼工作?沒有工具來找出到底發生了什麼。沒有社會的幫助,所以他們被困。他們又回到了MySQL。
      • 所有節點的資料損壞. What if there’s a bug that sprays badness into the write log across all of them and compaction or some other mechanism stops? Your read latencies increase. All your data is screwed and the data is gone.
      • 均衡不當而且很難修復. 非常常見,如果你有10個節點,你會注意到所有節點都在一個節點上,有一個手工處理方式,但會將所有負載分佈到一個單節點上
      • 權威資料失效. 叢集方案是很智慧的。In one case they bring in a new secondary. At about 80% the secondary says it’s primary and the primary goes to secondary and you’ve lost 20% of the data. Losing 20% of the data is worse than losing all of it because you don’t know what you’ve lost.

分片(sharding) – 全憑人手

  • 裁決: 分片是贏家。我覺得他們分片的方案與Flicker非常相似。
  • 特點:
    • 如果去掉叢集方式下所有不好的特點,就得到了分片。
    • 人工對資料進行分佈。
    • 不移動資料。
    • 通過切分資料來分擔負荷。
    • 節點不知道其它節點的存在。某些主節點控制一切。
  • 優點:
    • 可以通過切分資料庫來擴大容量。
    • 在空間上分佈資料。
    • 高可用。
    • 負載均衡。
    • 放置資料的演算法十分簡單。這是最主要的原因。雖然存在單點(SPOF),但只是很小的一段程式碼,而不是複雜到爆的叢集管理器。過了第一天就知道有沒有問題。
    • ID的生成很簡單。
  • 缺點:
    • 無法執行大多數連線。
    • 沒有事務功能。可能會出現寫入某個資料庫失敗、而寫入其它庫成功的情況。
    • 許多約束只能轉移到應用層實現。
    • schema的修改需要更多的規劃。
    • 如果要出報表,必須在所有分片上分別執行查詢,然後自己把結果合起來。
    • 連線只能轉移到應用層實現。
    • 應用必須應付以上所有的問題。

何時選擇分片?

  • 當有幾TB的資料時,應該儘快分片。
  • 當Pin錶行數達到幾十億,索引超出記憶體容量,被交換到磁碟時。
  • 他們選出一個最大的表,放入單獨的資料庫。
  • 單個資料庫耗盡了空間。
  • 然後,只能分片。

分片的過渡

  • 過渡從一個特性的凍結開始。
  • 確認分片該達到什麼樣的效果——希望盡少的執行查詢以及最少數量的資料庫去呈現一個頁面。
  • 剔除所有的MySQL join,將要做join的表格載入到一個單獨的分片去做查詢。
  • 新增大量的快取,基本上每個查詢都需要被快取。
  • 這個步驟看起來像:
  • 1 DB + Foreign Keys + Joins
  • 1 DB + Denormalized + Cache
  • 1 DB + Read Slaves + Cache
  • Several functionally sharded DBs+Read Slaves+Cache
  • ID sharded DBs + Backup slaves + cache
  • 早期的只讀奴節點一直都存在問題,因為存在slave lag。讀任務分配給了奴節點,然而主節點並沒有做任何的備份記錄,這樣就像一條記錄丟失。之後Pinterest使用快取解決了這個問題。
  • Pinterest擁有後臺指令碼,資料庫使用它來做備份。檢查完整性約束、引用。
  • 使用者表並不進行分片。Pinterest只是使用了一個大型的資料庫,並在電子郵件和使用者名稱上做了相關的一致性約束。如果插入重複使用者,會返回失敗。然後他們對分片的資料庫做大量的寫操作。

如何進行分片?

  • 可以參考Cassandra的ring模型、Membase以及Twitter的Gizzard。
  • 堅信:節點間資料傳輸的越少,你的架構越穩定。
  • Cassandra存在資料平衡和所有權問題,因為節點們不知道哪個節點儲存了另一部分資料。Pinterest認為應用程式需要決定資料該分配到哪個節點,那麼將永遠不會存在問題。
  • 預計5年內的增長,並且對其進行預分片思考。
  • 初期可以建立一些虛擬分片。8個物理伺服器,每個512DB。所有的資料庫都裝滿表格。
  • 為了高有效性,他們一直都執行著多主節點冗餘模式。每個主節點都會分配給一個不同的可用性區域。在故障時,該主節點上的任務會分配給其它的主節點,並且重新部署一個主節點用以代替。
  • 當資料庫上的負載加重時:
  • 先著眼節點的任務交付速度,可以清楚是否有問題發生,比如:新特性,快取等帶來的問題。
  • 如果屬於單純的負載增加,Pinterest會分割資料庫,並告訴應用程式該在何處尋找新的節點。
  • 在分割資料庫之前,Pinterest會給這些主節點加入一些奴節點。然後置換應用程式程式碼以匹配新的資料庫,在過渡的幾分鐘之內,資料會同時寫入到新舊節點,過渡結束後將切斷節點之間的通道。

ID結構

  • 一共64位
  • 分片ID:16位
  • Type:10位—— Board、User或者其它物件型別
  • 本地ID——餘下的位數用於表中ID,使用MySQL自動遞增。
  • Twitter使用一個對映表來為物理主機對映ID,這將需要備份;鑑於Pinterest使用AWS和MySQL查詢,這個過程大約需要3毫秒。Pinterest並沒有讓這個額外的中間層參與工作,而是將位置資訊構建在ID裡。
  • 使用者被隨機分配在分片中間。
  • 每個使用者的所有資料(pin、board等)都存放在同一個分片中,這將帶來巨大的好處,避免了跨分片的查詢可以顯著的增加查詢速度。
  • 每個board都與使用者並列,這樣board可以通過一個資料庫處理。
  • 分片ID足夠65536個分片使用,但是開始Pinterest只使用了4096個,這允許他們輕易的進行橫向擴充套件。一旦使用者資料庫被填滿,他們只需要增加額外的分片,然後讓新使用者寫入新的分片就可以了。

查詢

  • 如果存在50個查詢,舉個例子,他們將ID分割且並行的執行查詢,那麼延時將達到最高。
  • 每個應用程式都有一個配置檔案,它將給物理主機對映一個分片範圍。
  • “sharddb001a”: : (1, 512)
  • “sharddb001b”: : (513, 1024)——主要備份主節點
  • 如果你想查詢一個ID坐落在sharddb003a上的使用者:
  • 將ID進行分解
  • 在分片對映中執行查詢
  • 連線分片,在資料庫中搜尋型別。並使用本地ID去尋找這個使用者,然後返回序列化資料。

物件和對映

  • 所有資料都是物件(pin、board、user、comment)或者對映(使用者由baord,pin有like)。
  • 針對物件,每個本地ID都對映成MySQL Blob。開始時Blob使用的是JSON格式,之後會給轉換成序列化的Thrift。
  • 對於對映來說,這裡有一個對映表。你可以為使用者讀取board,ID包含了是時間戳,這樣就可以體現事件的順序。
  • 同樣還存在反向對映,多表對多表,用於查詢有哪些使用者喜歡某個pin這樣的操作。
  • 模式的命名方案是:noun_verb_noun: user_likes_pins, pins_like_user。
  • 只能使用主鍵或者是索引查詢(沒有join)。
  • 資料不會向叢集中那樣跨資料的移動,舉個例子:如果某個使用者坐落在20分片上,所有他資料都會並列儲存,永遠不會移動。64位ID包含了分片ID,所以它不可能被移動。你可以移動物理資料到另一個資料庫,但是它仍然與相同分片關聯。
  • 所有的表都存放在分片上,沒有特殊的分片,當然用於檢測使用者名稱衝突的巨型表除外。
  • 不需要改變模式,一個新的索引需要一個新的表。
  • 因為鍵對應的值是blob,所以你不需要破壞模式就可以新增欄位。因為blob有不同的版本,所以應用程式將檢測它的版本號並且將新記錄轉換成相應的格式,然後寫入。所有的資料不需要立刻的做格式改變,可以在讀的時候進行更新。
  • 巨大的勝利,因為改變表格需要在上面加幾個小時甚至是幾天的鎖。如果你需要一個新的索引,你只需要建立一張新的表格,並填入內容;在不需要的時候,丟棄就好。

呈現一個使用者檔案介面

  • 從URL中取得使用者名稱,然後到單獨的巨型資料庫中查詢使用者的ID。
  • 獲取使用者ID,並進行拆分
  • 選擇分片,並進入
  • SELECT body from users WHERE id = <local_user_id>
  • SELECT board_id FROM user_has_boards WHERE user_id=<user_id>
  • SELECT body FROM boards WHERE id IN (<boards_ids>)
  • SELECT pin_id FROM board_has_pins WHERE board_id=<board_id>
  • SELECT body FROM pins WHERE id IN (pin_ids)
  • 所有呼叫都在快取中進行(Memcache或者Redis),所以在實踐中並沒有太多連線資料庫的後端操作。

指令碼相關

  • 當你過渡到一個分片架構,你擁有兩個不同的基礎設施——沒有進行分片的舊系統和進行分片的新系統。指令碼成為了新舊系統之間資料傳輸的橋樑。
  • 移動5億的pin、16億的follower行等。
  • 不要輕視專案中的這一部分,Pinterest原認為只需要2個月就可以完成資料的安置,然而他們足足花了4至5個月時間,別忘了期間他們還凍結了一項特性。
  • 應用程式必須同時對兩個系統插入資料。
  • 一旦確認所有的資料都在新系統中就位,就可以適當的增加負載來測試新後端。
  • 建立一個指令碼農場,僱傭更多的工程師去加速任務的完成。讓他們做這些表格的轉移工作。
  • 設計一個Pyres副本,一個到GitHub Resque佇列的Python的介面,這個佇列建立在Redis之上。支援優先順序和重試,使用Pyres取代Celery和RabbitMQ更是讓他們受益良多。
  • 處理中會產生大量的錯誤,使用者可能會發現類似丟失board的錯誤;必須重複的執行任務,以保證在資料的處理過程中不會出現暫時性的錯誤。

動態

  • 最初試圖給開發者一個分片系統。每個開發者都能擁有自己的MySQL伺服器,但事情發生了快速的變化,導致不能工作。變
  • 去了Facebook的模式:每個人可以獲得一切。所以,你不得不非常謹慎

未來發展方向

  • 基於服務的架構。
    • 當他們開始看到了很多的資料庫負載,便像產卵一樣,導致了很多的應用伺服器和其他伺服器堆在一起。所有這些伺服器連線到MySQL和Memcache。這意味著有30K上的memcache連線了一對夫婦演出的RAM引起的memcache守護程式交換。
    • 作為一個修補程式,這些都是移動的服務架構。有一個跟隨服務,例如,將只回答跟隨查詢。此隔離的機器數目至30訪問資料庫和快取記憶體,從而減少了連線。
    • 幫助隔離功能。幫助組織,解決和支援這些服務的團隊。幫助開發人員,為了安全開發人員不能訪問其他服務。

學到的知識

  • 為了應對未來的問題,讓其保持簡單。
  • 讓其變的有趣。只要應用程式還在使用,就會有很多的工程師加入,過於複雜的系統將會讓工作失去樂趣。讓架構保持簡單就是大的勝利,新的工程師從入職的第一週起就可以對專案有所貢獻。
  • 當你把事物用至極限時,這些技術都會以各自不同的方式發生故障。
  • 如果你的架構應對增長所帶來的問題時,只需要簡單的投入更多的主機,那麼你的架構含金量十足。
  • 叢集管理演算法本身就用於處理SPOF,如果存在漏洞的話可能就會影響到每個節點。
  • 為了快速的增長,你需要為每次負載增加的資料進行均勻分配。
  • 在節點間傳輸的資料越少,你的架構越穩定。這也是他們棄叢集而選擇分片的原因
  • 一個面向服務的架構規則。拆分功能,可以幫助減少連線、組織團隊、組織支援以及提升安全性。
  • 搞明白自己究竟需要什麼。為了匹配願景,不要怕丟棄某些技術,甚至是整個系統的重構。
  • 不要害怕丟失一點資料。將使用者資料放入記憶體,定期的進行持久化。失去的只是幾個小時的資料,但是換來的卻是更簡單、更強健的系統!

 

相關文章