1. 程式人生 > >Pinterest談實戰經驗:如何在兩年內實現零到數百億的月訪問

Pinterest談實戰經驗:如何在兩年內實現零到數百億的月訪問

Pinterest一直保持著指數增長,每一個半月都會翻一翻。在兩年內,他們實現了從0到數百億的月PV;從開始的兩個創始人加一個工程師增長到現在超過40個工程師,從一個小型的MySQL伺服器增長到180個Web Enigne、240個API Enigne、88個MySQL DB(cc2.8xlarge,每個DB都會配置一個奴節點)、110個Redis Instance以及200個Mmecache Instance。

在一個名為 《Scaling Pinterest》 的主題演講上,Pinterest的Yashwanth Nelapati和 Marty Weiner為我們講述了這個戲劇性的過程。當然擴充套件到當下規模,Pinterest在眾多選擇中不可避免的走了許多的彎路,而Todd Hoff認為其中最寶貴的經驗該歸結於以下兩點:

  1. 如果你的架構應對增長所帶來的問題時,只需要簡單的投入更多的主機,那麼你的架構含金量十足。
  2. 當你把事物用至極限時,這些技術都會以各自不同的方式發生故障,這導致他們對工具的選擇有著特殊的偏好:成熟、簡單、優秀、知名、被更多的使用者喜愛、更好的支援、穩定且傑出的表現、通常情況下無故障以及免費。使用這些標準,他們選擇了MySQL、Solr、Memcache、Redis、Cassandra,同時還拋棄了MongoDB。

同樣這兩個點是有關聯的,符合第二個原則的工具就可以通過投入更多的主機進行擴充套件。即使負載的增加,專案也不會出現很多故障。即使真的出現難以解決的問題,至少有一個社群去尋找問題解決的方案。一旦你選擇過於複雜和挑剔的工具,在擴充套件的道路上將充滿荊棘。

需要注意的是所有他們選擇的工具都依靠增加分片來進行擴充套件,而非通過叢集。講話中還闡述了為什麼分片優於叢集以及如何進行分片,這些想法可能是之前你聞所未聞的。

下面就看一下Pinterest擴充套件的階段性時間軸:

專案背景

  • Pins是由其它零零碎碎資訊集合成的圖片,顯示了對客戶重要的資訊,並且連結到它所在的位置。
  • Pinterest是一個社交網路,你可以follow(關注)其它人以及board。
  • 資料庫:Pinterest的使用者擁有board,而每個board都包含pin;follow及repin人際關係、驗證資訊。

1. 2010年3月釋出——尋找真我的時代

在那時候,你甚至不知道需要建立一個什麼樣的產品。你有想法,所以你快速的迭代以及演變。而最終你將得到一些很小的MySQL查詢,而這些查詢在現實生活中你從未進行過。

Pinterest初期階段的一些數字:

  • 2個創始人
  • 1個工程師
  • Rackspace
  • 1個小的網路引擎
  • 1個小的MySQL資料庫
  • 2011年11月

仍然是小規模,產品通過使用者反饋進行演變後的數字是:

  • Amazon EC2 + S3 + CloudFront
  • 1 NGinX, 4 Web Engines (用於冗餘,不全是負載)
  • 1 MySQL DB + 1 Read Slave (用於主節點故障情況)
  • 1 Task Queue + 2 Task Processors
  • 1 MongoDB (用於計數)
  • 2 Engineers

2. 貫穿2011年——實驗的時代

邁上瘋狂增長的腳步,基本上每1個半月翻一翻。

  • 當你增長的如此之快,每一天每一星期你可能都需要打破或者拋棄一些東西。
  • 在這個時候,他們閱讀大量的論文,這些論文都闡述著只需要新增一臺主機問題就會得以解決。他們著手新增許多技術,隨後又不得不放棄。
  • 於是出現了一些很奇怪的結果 
  • Amazon EC2 + S3 + CloudFront
  • 2NGinX, 16 Web Engines + 2 API Engines
  • 5 Functionally Sharged MySQL DB + 9 read slaves
  • 4 Cassandra Nodes
  • 15 Membase Nodes (3 separate clusters)
  • 8 Memcache Nodes
  • 10 Redis Nodes
  • 3 Task Routers + 4 Task Processors
  • 4 Elastic Search Nodes
  • 3 Mongo Clusters
  • 3個工程師
  • 5個主資料庫技術,只為了獨立其中的資料。
  • 增長太快以至於MySQL疲於奔命,所有其它的技術也達到了極限。
  • 當你把事物用至極限時,這些技術都會以各自不同的方式出錯。
  • 開始拋棄一些技術,並且自我反省究竟需要些什麼,基本上重做了所有的架構。

3. 2012年2月——成熟的時代

  • 在重做了所有的架構後,系統呈現瞭如下狀態
  • Amazon EC2 + S3 + Akamai, ELB
  • 90 Web Engines + 50 API Engines
  • 66 MySQL DBs (m1.xlarge) +,每個資料庫都配備了奴節點
  • 59 Redis Instances
  • 51 Memcache Instances
  • 1 Redis Task Manager + 25 Task Processors
  • Sharded Solr
  • 6個工程師
  • 現在採用的技術是被分片的MySQL、Redis、Memcache和Solr,有點在於這些技術都很簡單很成熟。
  • 網路傳輸增長仍然保持著以往的速度,而iPhone傳輸開始走高。

4. 2012年10月12日 —— 收穫的季節

大約是1月份的4倍

  • 現在的資料是:
  • Amazon EC2 + S3 + Edge Cast,Akamai, Level 3
  • 180 Web Engines + 240 API Engines
  • 88 MySQL DBs (cc2.8xlarge) ,同樣每個資料庫都有一個奴節點
  • 110 Redis Instances
  • 200 Memcache Instances
  • 4 Redis Task Manager + 80 Task Processors
  • Sharded Solr
  • 40個工程師(仍在增長)
  • 需要注意的是,如今的架構已趨近完美,應對增長只需要投入更多的主機。
  • 當下已開始轉移至SSD

下面一覽該演講中的乾貨,決策的制定:

為什麼會選擇EC2和S3

  1. 相當好的可靠性,即使資料中心發生故障。多租戶會增加風險,但是也不是太壞。
  2. 良好的報告和支援。它們(EC2和S3)有著良好的架構,並且知道問題所在。
  3. 完善的周邊設施,特別是在你需要快速增長時。你可以從APP Engine處獲得maged cache、負載均衡、MapReduce、資料庫管理以及其它你不想自己動手編寫的元件,這可以加速你應用程式的部署,而在你工程師空閒時,你可以著手編寫你需要的一切。
  4. 新的例項可以在幾秒內就緒,這就是雲的力量;特別是在只有兩個工程師的初期,不需要去擔心容量規劃,更不需要花兩個星期去建立自己的Memcache,你可以在數分鐘內新增10個Memcached。
  5. 缺點:有限的選擇。直到最近,才可以選擇使用SSD,同時無法獲得太大的記憶體配置。
  6. 優點:你不需要給大量的主機進行不同的配置。

為什麼會選擇MySQL

  1. 非常成熟。
  2. 非常穩定。不會宕機,並且不會丟失資料。
  3. 在招聘上具有優勢,市場上有大把的人才。
  4. 在請求呈直線上升時,仍能將相應時間控制在一定的範圍內,有些資料庫技術在面對請求的飆升時表現並不是很好。
  5. 非常好的周邊軟體支援——XtraBackup、Innotop、Maatkit。
  6. 可以從類似Percona這樣的公司得到優秀的技術支援。
  7. 開源(免費)——這一點非常重要,特別是在資金缺乏的初期

為什麼使用Memcache

  • 非常成熟。
  • 非常簡單。可以當成是一個socket雜湊表
  • 傑出穩定的表現
  • 知名併為大量使用者喜愛
  • 永不崩潰
  • 開源

為什麼選擇Redis

  • 雖然還不夠成熟,但是非常簡單及優秀
  • 提供了大量的資料結構型別
  • 提供多種的選擇進行持久化和備份:你可以備份而非持久化,選擇備份的話你還可以選擇多久備份一次;同樣你還可以選擇使用什麼方式進行持久化,比如MySQL等。
  • Home feed被儲存在Redis上,每3個小時儲存一次;然而並不是3個小時持久化一次,只是簡單的每3個小時備份一次。
  • 如果你儲存資料的主機發生故障,丟失的也只是備份週期內的資料。雖然不是完全可靠,但是非常簡單。避免了複雜的持久化及複製,這樣的架構簡單且便宜。
  • 知名併為大量使用者喜愛
  • 穩定且傑出的表現
  • 很少出故障。有一些專有的故障模型,你需要學會解決。這也是成熟的優勢,只需要學習就可以解決。
  • 開源

Solr

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

叢集vs.分片

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

叢集——所有的操作都是通過自動化

  • 比如:Cassandra、MemBase、HBase
  • 結論:沒有安全感,將來可能會比較成熟,但是當下這個解決方案中還存在太多的複雜性和故障點。
  • 特性:
  • 資料自動分佈
  • 節點間轉移資料
  • 需要平衡分配
  • 節點間的相互通訊,需要做很多措施用於防止干擾、無效傳遞及協商。
  • 優點:
  • 自動擴充套件你的資料儲存,最起碼論文中是這麼說的。
  • 便於安裝
  • 資料上的空間分佈及機房共置。你可以在不同區域建立資料中心,資料庫會幫你打理好一切。
  • 高有效性
  • 負載平衡
  • 不存在單點故障
  • 缺點:
  • 仍然不成熟。
  • 本質上說還很複雜。一大堆的節點必須對稱協議,這一點非常難以解決。
  • 缺少社群支援。社群的討論因為產品方向的不同而不能統一,而在每個正營中也缺乏強有力的支援。
  • 缺乏領域內資深工程師,可能大多數的工程師都還未使用過Cassandra。
  • 困難、沒有安全感的機制更新。這可能是因為這些技術都使用API並且只在自己的領域內通行,這導致了複雜的升級路徑。
  • 叢集管理演算法本身就用於處理SPOF(單點故障),如果存在漏洞的話可能就會影響到每個節點。
  • 叢集管理器程式碼非常複雜,並且需要在所有節點上重複,這就可能存在以下的故障模式:
  • 資料平衡失控。當給叢集中新增新的主機時,可能因為資料的拷貝而導致叢集效能下降。那麼你該做什麼?這裡不存在去發現問題所在的工具。沒有社群可以用來求助,同樣你也被困住了,這也是Pinterest回到MySQL的原因。
  • 跨節點的資料損壞。如果這裡存在一個漏洞,這個漏洞可能會影響節點間的日誌系統和壓縮等其它元件?你的讀延時增加,所有的資料都會陷入麻煩以及丟失。
  • 錯誤負載平衡很難被修復,這個現象十分普遍。如果你有10個節點,並且你注意到所有的負載都被堆積到一個節點上。雖然可以手動處理,但是之後系統還會將負載都加之一個節點之上。
  • 資料所有權問題,主次節點轉換時的資料丟失。叢集方案是非常智慧的,它們會在特定的情況下完成節點權利的轉換,而主次節點切換的過程中可能會導致資料的部分丟失,而丟失部分資料可能比丟失全部還糟糕,因為你不可能知道你究竟丟失了哪一部分。

分片——所有事情都是手動的

  • 結論:它是獲勝者。Todd Hoff還認為他們的分片架構可能與Flickr架構類似。
  • 特性:
  • 分片可以讓你擺脫叢集方案中所有不想要的特性。
  • 資料需要手動的分配。
  • 資料不會移動。Pinterest永遠都不會在節點間移動,儘管有些人這麼做,這讓他們在一定範圍內站的更高。
  • 通過分割資料的方式分配負載。
  • 節點並沒有互相通訊,使用一些主節點控制程式的執行。
  • 優點:
  • 可以分割你的資料庫以提高效能。
  • 空間分佈及放置資料
  • 高有效性
  • 負載平衡
  • 放置資料的演算法非常簡單。主要原因是,用於處理單點故障的程式碼只有區區的半頁,而不是一個複雜的叢集管理器。並且經過短暫的測試就知道它是否能夠正常工作。
  • ID生成非常簡單
  • 缺點:
  • 不可以執行大多數的join。
  • 失去所有事務的能力。在一個數據庫上的插入可能會成功,而在另一個上會失敗。
  • 許多約束必須放到應用程式層。
  • 模式的轉變需要從長計議。
  • 報告需要在所有分片上執行查詢,然後需要手動的進行聚合。
  • Join在應用程式層執行。
  • 應用程式必須容忍以上所有問題。

什麼時候進行分片

  1. 如果你的專案擁有PB級的資料,那麼你需要立刻對其進行分片。
  2. Pin表格擁有百萬行索引,索引大小已經溢位記憶體並被存入了磁碟。
  3. Pinterest使用了最大的表格,並將它們(這些索引)放入自己的資料庫。
  4. 然後果斷的超過了單資料庫容量。
  5. 接著Pinterest必須進行分片。

分片的過渡

  • 過渡從一個特性的凍結開始。
  • 確認分片該達到什麼樣的效果——希望盡少的執行查詢以及最少數量的資料庫去呈現一個頁面。
  • 剔除所有的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有不同的版本,所以應用程式將檢測它的版本號並且將新記錄轉換成相應的格式,然後寫入。所有的資料不需要立刻的做格式改變,可以在讀的時候進行更新。
  • 巨大的勝利,因為改變表格需要在上面加幾個小時甚至是幾天的鎖。如果你需要一個新的索引,你只需要建立一張新的表格,並填入內容;在不需要的時候,丟棄就好。

呈現一個使用者檔案介面

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

指令碼相關

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

開發相關

  • 開始嘗試只給開發者開放系統的一部分——他們每個人都擁有自己的MySQL伺服器等,但是事情改變的太快,以至於這個模式根本無法實行。
  • 轉變成Facebook模式,每個人都可以訪問所有東西,所以不得不非常小心。

未來的方向

  • 基於服務的架構
  • 當他們發現大量的資料庫負載,他們開始佈置大量的應用程式伺服器和一些其它的伺服器,所有這些伺服器都連線至MySQL和Memcache。這意味著在Memcache上將存在3萬的連線,這些連線將佔用幾個G的記憶體,同時還會產生大量的Memcache守護程序。
  • 為了解決這個問題,將這些工作轉移到了一個服務架構。比如:使用一個follower服務,這個服務將專注處理follower查詢。這將接下30臺左右的主機去連線資料庫和快取,從而減少了連線的數量。
  • 對功能進行隔離,各司其職。讓一個服務的開發者不能訪問其它的服務,從而杜絕安全隱患。

學到的知識

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