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認爲其中最寶貴的經驗該歸結於如下兩點:算法
若是你的架構應對增加所帶來的問題時,只須要簡單的投入更多的主機,那麼你的架構含金量十足。數據庫
當你把事物用至極限時,這些技術都會以各自不一樣的方式發生故障,這致使他們對工具的選擇有着特殊的偏好:成熟、簡單、優秀、知名、被更多的用戶喜好、更好的支持、穩定且傑出的表現、一般狀況下無端障以及免費。使用這些標準,他們選擇了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
至關好的可靠性,即便數據中心發生故障。多租戶會增長風險,可是也不是太壞。
良好的報告和支持。它們(EC2和S3)有着良好的架構,而且知道問題所在。
完善的周邊設施,特別是在你須要快速增加時。你能夠從APP Engine處得到maged cache、負載均衡、MapReduce、數據庫管理以及其它你不想本身動手編寫的組件,這能夠加速你應用程序的部署,而在你工程師空閒時,你能夠着手編寫你須要的一切。
新的實例能夠在幾秒內就緒,這就是雲的力量;特別是在只有兩個工程師的初期,不須要去擔憂容量規劃,更不須要花兩個星期去創建本身的Memcache,你能夠在數分鐘內添加10個Memcached。
缺點:有限的選擇。直到最近,才能夠選擇使用SSD,同時沒法得到太大的內存配置。
優勢:你不須要給大量的主機進行不一樣的配置。
爲何會選擇MySQL
很是成熟。
很是穩定。不會宕機,而且不會丟失數據。
在招聘上具備優點,市場上有大把的人才。
在請求呈直線上升時,仍能將相應時間控制在必定的範圍內,有些數據庫技術在面對請求的飆升時表現並非很好。
很是好的周邊軟件支持——XtraBackup、Innotop、Maatkit。
能夠從相似Percona這樣的公司獲得優秀的技術支持。
開源(免費)——這一點很是重要,特別是在資金缺少的初期
爲何使用Memcache
很是成熟。
很是簡單。能夠當成是一個socket哈希表
傑出穩定的表現
知名併爲大量用戶喜好
永不崩潰
開源
爲何選擇Redis
雖然還不夠成熟,可是很是簡單及優秀
提供了大量的數據結構類型
提供多種的選擇進行持久化和備份:你能夠備份而非持久化,選擇備份的話你還能夠選擇多久備份一次;一樣你還能夠選擇使用什麼方式進行持久化,好比MySQL等。
Home feed被儲存在Redis上,每3個小時保存一次;然而並非3個小時持久化一次,只是簡單的每3個小時備份一次。
若是你存儲數據的主機發生故障,丟失的也只是備份週期內的數據。雖然不是徹底可靠,可是很是簡單。避免了複雜的持久化及複製,這樣的架構簡單且便宜。
知名併爲大量用戶喜好
穩定且傑出的表現
不多出故障。有一些專有的故障模型,你須要學會解決。這也是成熟的優點,只須要學習就能夠解決。
開源
Solr
只須要幾分鐘的安裝時間,就能夠投入使用
不能擴展到多於一臺的機器上(最新版本並不是如此)
嘗試彈性搜索,可是以Pinterest的規模來講,可能會由於零碎文件和查詢太多而產生問題。
選擇使用Websolr,可是Pinterest擁有搜索團隊,未來可能會開發本身的版本。
集羣vs.分片
在迅速擴展的過程當中,Pinterest認識到每次負載的增長,都須要均勻的傳播他們的數據。
針對問題先肯定解決方案的範圍,他們選擇的範圍是集羣和分片之間的一系列解決方案。
集羣——全部的操做都是經過自動化
好比:Cassandra、MemBase、HBase
結論:沒有安全感,未來可能會比較成熟,可是當下這個解決方案中還存在太多的複雜性和故障點。
特性:
數據自動分佈
節點間轉移數據
須要平衡分配
節點間的相互通訊,須要作不少措施用於防止干擾、無效傳遞及協商。
優勢:
自動擴展你的數據存儲,最起碼論文中是這麼說的。
便於安裝
數據上的空間分佈及機房共置。你能夠在不一樣區域創建數據中心,數據庫會幫你打理好一切。
高有效性
負載平衡
不存在單點故障
缺點:
仍然不成熟。
本質上說還很複雜。一大堆的節點必須對稱協議,這一點很是難以解決。
缺乏社區支持。社區的討論由於產品方向的不一樣而不能統一,而在每一個正營中也缺少強有力的支持。
缺少領域內資深工程師,可能大多數的工程師都還未使用過Cassandra。
困難、沒有安全感的機制更新。這多是由於這些技術都使用API而且只在本身的領域內通行,這致使了複雜的升級路徑。
集羣管理算法自己就用於處理SPOF(單點故障),若是存在漏洞的話可能就會影響到每一個節點。
集羣管理器代碼很是複雜,而且須要在全部節點上重複,這就可能存在如下的故障模式:
數據平衡失控。當給集羣中添加新的主機時,可能由於數據的拷貝而致使集羣性能降低。那麼你該作什麼?這裏不存在去發現問題所在的工具。沒有社區能夠用來求助,一樣你也被困住了,這也是Pinterest回到MySQL的緣由。
跨節點的數據損壞。若是這裏存在一個漏洞,這個漏洞可能會影響節點間的日誌系統和壓縮等其它組件?你的讀延時增長,全部的數據都會陷入麻煩以及丟失。
錯誤負載平衡很難被修復,這個現象十分廣泛。若是你有10個節點,而且你注意到全部的負載都被堆積到一個節點上。雖然能夠手動處理,可是以後系統還會將負載都加之一個節點之上。
數據全部權問題,主次節點轉換時的數據丟失。集羣方案是很是智能的,它們會在特定的狀況下完成節點權利的轉換,而主次節點切換的過程當中可能會致使數據的部分丟失,而丟失部分數據可能比丟失所有還糟糕,由於你不可能知道你究竟丟失了哪一部分。
分片——全部事情都是手動的
結論:它是獲勝者。Todd Hoff還認爲他們的分片架構可能與Flickr架構相似。
特性:
分片可讓你擺脫集羣方案中全部不想要的特性。
數據須要手動的分配。
數據不會移動。Pinterest永遠都不會在節點間移動,儘管有些人這麼作,這讓他們在必定範圍內站的更高。
經過分割數據的方式分配負載。
節點並無互相通訊,使用一些主節點控制程序的運行。
優勢:
能夠分割你的數據庫以提升性能。
空間分佈及放置數據
高有效性
負載平衡
放置數據的算法很是簡單。主要緣由是,用於處理單點故障的代碼只有區區的半頁,而不是一個複雜的集羣管理器。而且通過短暫的測試就知道它是否可以正常工做。
ID生成很是簡單
缺點:
不能夠執行大多數的join。
失去全部事務的能力。在一個數據庫上的插入可能會成功,而在另外一個上會失敗。
許多約束必須放到應用程序層。
模式的轉變須要從長計議。
報告須要在全部分片上執行查詢,而後須要手動的進行聚合。
Join在應用程序層執行。
應用程序必須容忍以上全部問題。
何時進行分片
若是你的項目擁有PB級的數據,那麼你須要馬上對其進行分片。
Pin表格擁有百萬行索引,索引大小已經溢出內存並被存入了磁盤。
Pinterest使用了最大的表格,並將它們(這些索引)放入本身的數據庫。
而後果斷的超過了單數據庫容量。
接着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有不一樣的版本,因此應用程序將檢測它的版本號而且將新記錄轉換成相應的格式,而後寫入。全部的數據不須要馬上的作格式改變,能夠在讀的時候進行更新。
巨大的勝利,由於改變表格須要在上面加幾個小時甚至是幾天的鎖。若是你須要一個新的索引,你只須要創建一張新的表格,並填入內容;在不須要的時候,丟棄就好。
呈現一個用戶文件界面
從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。這意味着在Memcache上將存在3萬的鏈接,這些鏈接將佔用幾個G的內存,同時還會產生大量的Memcache守護進程。
爲了解決這個問題,將這些工做轉移到了一個服務架構。好比:使用一個follower服務,這個服務將專一處理follower查詢。這將接下30臺左右的主機去鏈接數據庫和緩存,從而減小了鏈接的數量。
對功能進行隔離,各司其職。讓一個服務的開發者不能訪問其它的服務,從而杜絕安全隱患。
學到的知識
爲了應對將來的問題,讓其保持簡單。
讓其變的有趣。只要應用程序還在使用,就會有不少的工程師加入,過於複雜的系統將會讓工做失去樂趣。讓架構保持簡單就是大的勝利,新的工程師從入職的第一週起就能夠對項目有所貢獻。
當你把事物用至極限時,這些技術都會以各自不一樣的方式發生故障。
若是你的架構應對增加所帶來的問題時,只須要簡單的投入更多的主機,那麼你的架構含金量十足。
集羣管理算法自己就用於處理SPOF,若是存在漏洞的話可能就會影響到每一個節點。
爲了快速的增加,你須要爲每次負載增長的數據進行均勻分配。
在節點間傳輸的數據越少,你的架構越穩定。這也是他們棄集羣而選擇分片的緣由。
一個面向服務的架構規則。拆分功能,能夠幫助減小鏈接、組織團隊、組織支持以及提高安全性。
搞明白本身究竟須要什麼。爲了匹配願景,不要怕丟棄某些技術,甚至是整個系統的重構。
不要懼怕丟失一點數據。將用戶數據放入內存,按期的進行持久化。失去的只是幾個小時的數據,可是換來的倒是更簡單、更強健的系統!
原文連接: Scaling Pinterest - From 0 To 10s Of Billions Of Page Views A Month In Two Years (編譯/仲浩 審校/王旭東)