原文:Scaling Pinterest - From 0 To 10s Of Billions Of Page Views A Month In Two Yearshtml
譯文:兩年內從零到每個月十億 PV 的發展來談 Pinterest 的架構設計web
Pinterest正經歷了指數級曲線般的增加,每隔一個半月翻翻。在這兩年裏,Pinterest,從 每個月PV量0增加到10億,從兩名成立者和一個工程師成長爲四十個工程師,從一臺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架構的充滿戲劇化的傳奇故事。他們說若是能在一年半前飛速發展時能看到有人作相似題材的演講的話,他們就會有更多的選擇,以免本身在這一年半里作出的不少錯誤的決定。sql
這是一個很不錯的演講,充滿了使人驚訝的細節。同時這個演講也是很務實的,歸根結底,它帶來了可以讓你們選擇的策略。極度推薦!數據庫
這篇演講中有兩個我最爲看重的經驗:後端
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結構
- 分片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。
- 數據不會向集羣中那樣跨數據的移動,舉個例子:若是某個用戶坐落在20分片上,全部他數據都會並列存儲,永遠不會移動。64位ID包含了分片ID,因此它不可能被移動。你能夠移動物理數據到另外一個數據庫,可是它仍然與相同分片關聯。
- 全部的表都存放在分片上,沒有特殊的分片,固然用於檢測用戶名衝突的巨型表除外。
- 由於鍵對應的值是blob,因此你不須要破壞模式就能夠添加字段。由於blob有不一樣的版本,因此應用程序將檢測它的版本號而且將新記錄轉換成相應的格式,而後寫入。全部的數據不須要馬上的作格式改變,能夠在讀的時候進行更新。
- 巨大的勝利,由於改變表格須要在上面加幾個小時甚至是幾天的鎖。若是你須要一個新的索引,你只須要創建一張新的表格,並填入內容;在不須要的時候,丟棄就好。
呈現一個用戶文件界面
- 從URL中取得用戶名,而後到單獨的巨型數據庫中查詢用戶的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),因此在實踐中並無太多鏈接數據庫的後端操做。
腳本相關
- 當你過渡到一個分片架構,你擁有兩個不一樣的基礎設施——沒有進行分片的舊系統和進行分片的新系統。腳本成爲了新舊系統之間數據傳輸的橋樑。
- 不要輕視項目中的這一部分,Pinterest原認爲只須要2個月就能夠完成數據的安置,然而他們足足花了4至5個月時間,別忘了期間他們還凍結了一項特性。
- 一旦確認全部的數據都在新系統中就位,就能夠適當的增長負載來測試新後端。
- 創建一個腳本農場,僱傭更多的工程師去加速任務的完成。讓他們作這些表格的轉移工做。
- 設計一個Pyres副本,一個到GitHub Resque隊列的Python的接口,這個隊列創建在Redis之上。支持優先級和重試,使用Pyres取代Celery和RabbitMQ更是讓他們受益良多。
- 處理中會產生大量的錯誤,用戶可能會發現相似丟失board的錯誤;必須重複的運行任務,以保證在數據的處理過程當中不會出現暫時性的錯誤。
動態
將來發展方向
-
基於服務的架構。
-
當他們開始看到了不少的數據庫負載,便像產卵同樣,致使了不少的應用服務器和其餘服務器堆在一塊兒。全部這些服務器鏈接到MySQL和Memcache。這意味着有30K上的memcache鏈接了一對夫婦演出的RAM引發的memcache守護進程交換。
-
做爲一個修補程序,這些都是移動的服務架構。有一個跟隨服務,例如,將只回答跟隨查詢。此隔離的機器數目至30訪問數據庫和高速緩存,從而減小了鏈接。
-
幫助隔離功能。幫助組織,解決和支持這些服務的團隊。幫助開發人員,爲了安全開發人員不能訪問其餘服務。
學到的知識
- 讓其變的有趣。只要應用程序還在使用,就會有不少的工程師加入,過於複雜的系統將會讓工做失去樂趣。讓架構保持簡單就是大的勝利,新的工程師從入職的第一週起就能夠對項目有所貢獻。
- 當你把事物用至極限時,這些技術都會以各自不一樣的方式發生故障。
- 若是你的架構應對增加所帶來的問題時,只須要簡單的投入更多的主機,那麼你的架構含金量十足。
- 集羣管理算法自己就用於處理SPOF,若是存在漏洞的話可能就會影響到每一個節點。
- 爲了快速的增加,你須要爲每次負載增長的數據進行均勻分配。
- 在節點間傳輸的數據越少,你的架構越穩定。這也是他們棄集羣而選擇分片的緣由
- 一個面向服務的架構規則。拆分功能,能夠幫助減小鏈接、組織團隊、組織支持以及提高安全性。
- 搞明白本身究竟須要什麼。爲了匹配願景,不要怕丟棄某些技術,甚至是整個系統的重構。
- 不要懼怕丟失一點數據。將用戶數據放入內存,按期的進行持久化。失去的只是幾個小時的數據,可是換來的倒是更簡單、更強健的系統