[譯] Instagram 的分片與ID設計

專欄 | 九章算法
網址 | www.jiuzhang.com

原文標題:Sharding & IDs at Instagram
原文連接:Sharding & IDs at Instagram
摘要:Instagram 官方技術文檔,關於Instagram的系統設計,經典系統設計文章。
翻譯:金秋javascript

Instagram上有大量的數據,每分鐘就有超過25張的圖片和90個點贊。爲了確保全部重要的數據都能被合理存儲而且及時得被提取應用,咱們對數據進行了分片(sharding)——也就是說,咱們把數據放到多個桶(bucket)中,每一個桶裏都有一部分數據。java

咱們的應用服務器上運行的是Django, 後端數據庫是PostgreSQL。對數據分片首先要決定是否要保留PostgreSQL做爲主要的數據存儲庫,是否要採用其餘的數據庫。通過評估一些不一樣的數據庫解決方案,咱們最終肯定最適合的方案是在PostgreSQL數據庫集羣上實現數據分片。程序員

然而在把數據寫到數據庫以前,咱們還要解決如何給數據(例如Instagram上發佈的沒一張圖片)加上惟一標識符的問題。在單一數據庫上的典型解法——使用數據庫自帶的自增主鍵功能——在當數據須要被同時插入到多個數據庫時就不適用了。文章的下面就來說講咱們是如何解決這個問題的。web

開始前,咱們先列出系統所須要的全部重要的功能。面試

1. 產生的數據ID需是能夠按時間排序的。(好比對一列圖片數據的ID進行排序,能夠不須要提取太多圖片自己的信息)
2. 理想的ID是64位的。(這樣索引更小,存儲也更優,像Redis)
3. 系統要儘可能少的引用「可變更因素」——在不多工程師的狀況下還能夠擴張Instagram的很大一部分緣由就是,咱們相信簡單易懂的方案。複製代碼

1.現有的解決方案

現有不少產生ID的解決方案,咱們考慮的有如下這些:算法

1.1.由Web應用層生成ID

這種方案將ID的生成徹底交到應用層,而不是數據庫。例如,MongoDB的ObjectId,就是12字節長而且在最前面加上時間戳進行編碼。另外一個流行的方案是使用UUIDs。數據庫

優勢:編程

  1. 每一個應用線程獨立生成ID,最小的下降ID生成的失敗和競爭。
  2. 若是用時間戳做爲ID的起始部分,那麼ID能夠按時間排序。

缺點:後端

  1. 一般須要更多的存儲空間(96位或者更多)來確保ID的合理惟一性。
  2. 一些UUID類型徹底是隨機的,沒法排序。

1.2經過單獨的服務產生ID

例如:Twitter的Snowflake,是一個Thrift服務,使用了Apache Zookeeper來協調各個結點而且產生64爲的惟一ID。服務器

優勢:

  1. Snowflake的ID只有64位,是UUID的一半。
  2. 能夠放時間戳到ID頭,從而能夠按時間排序。
  3. 分佈式系統保證了系統結點不會掛掉。

缺點:

增長了複雜性,並且引入了更多的「可變更因素」(如ZooKeeper, Snowflake服務器)到系統構架中。

1.3.數據庫票據(DB Ticket)服務器

利用數據庫自帶的自增特性來確保惟一性。Flicker採用這一方法,不過還用了兩臺ticket數據庫(一個生成偶數,一個生成奇數)來避免單點失敗。

優勢:

數據庫好理解,帶有易預測的可擴張功能。

缺點:

  1. 最終會出現數據寫入的瓶頸(儘管Flicker稱在高擴展下沒有問題)。
  2. 須要管理多的兩臺服務器(或者EC2實例)。
  3. 若是單獨使用數據庫,會出現單點失效。若是使用多個數據庫,則不能保證ID能夠按時間排序。

在全部這些方案中,Twitter的snowflake是最接近的,可是生成ID所需的添加複雜性又和咱們的目標衝突。咱們的替換方案是採用概念上相近的方法,可是帶到PostgreSQL內部實現。

2.Instagram 的方案

咱們的分片系統是由上千個「邏輯」分片組成的,由代碼映射到少許的物理分片。

經過這個方法,咱們一開始用少數數據庫實現,慢慢擴展到更多個數據庫,只須要把部分邏輯分片從一臺數據庫轉移到另外一臺數據庫裏,不須要從新把數據從新聚合。咱們用到的PostgreSQL的schema的特性能夠輕鬆的實現計劃和管理。

Schema(不要和SQL單個表的schema搞混了)是PostgreSQL裏的一個邏輯分組功能。每個PostgreSQL數據庫都有好幾個schema,每個schema都有一到多個表。表名在沒個schema中都是惟一的,而不是每一個數據庫,默認狀況下,PostgreSQL會把全部數據都放在一個叫「public」的schema中。

在咱們的系統中每一個「邏輯」分片都是一個PostgreSQL schema, 每一個分片的表(好比照片的「點贊」功能)都存在每一個schema中。

咱們經過使用PL/PGSQL, PostgreSQL內部的編程語言,和PostgreSQL現有的自增功能來生成ID。

每一個ID都由下面幾個部分組成:

41位的毫秒級時間(用於產生41年的ID)
13位用來表示邏輯分片的ID
10位的自增序列,模上1024, 意味着每一個分片每毫秒能夠產生1024個ID複製代碼

下面經過一個例子說明:好比說如今是2011年的九月九日,咱們的紀元是從2011年的一月一號開始。重新紀元開始到此有1387263000毫秒,那麼咱們把這個數字左移41位來填滿ID的頭。

id = 1387263000 <<(64 – 41)

接下來, 咱們拿來咱們準備把這個數據插入的分片ID;若是咱們用戶ID是31341, 這個分片的ID是 31341%2000 → 1341。 咱們接下來把下面13爲填滿:

id |= 1341 << (64-41-13)

最後, 咱們用所剩的自增序列(每一個schema中每一個表中這個序列都是惟一的)來填滿的後面的位數。 假設這張表中已經有了5000個ID; 咱們下一個數據就是5001,咱們把它模上1024獲得:

id |= (5001%1024)

咱們就獲得了咱們的ID, 咱們把這個id做爲insert中的RETURNING返回給應用層。

下面是是實現以上過程的PL/PGSQL代碼(這裏用的schema是insta5)

要生成表示執行下面的部分:

成了!咱們獲得了應用中惟一的主鍵(還有一個好處是,ID中包含了分片ID能夠用來輕鬆映射)。咱們已經把這個方法用在產品中,而且對結果感到很是滿意。


歡迎關注個人微信公衆號:九章算法(ninechapter)。
精英程序員交流社區,按期發佈面試題、面試技巧、求職信息等

九章算法,IT教育領域的深耕者
相關文章
相關標籤/搜索