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

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

在一個名爲 《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. 不要懼怕丟失一點數據。將用戶數據放入內存,按期的進行持久化。失去的只是幾個小時的數據,可是換來的倒是更簡單、更強健的系統!

                      原文連接: Scaling Pinterest - From 0 To 10s Of Billions Of Page Views A Month In Two Years (編譯/仲浩 審校/王旭東)

                      相關文章
                      相關標籤/搜索