Instagram 的ID生成策略[翻譯]


     項目中遇到一個ID生成策略的需求:需要在系統中為每個用戶分配一個ID用作以后的用戶標示。這個需求應該是非常普遍的,對於使用人數較少的系統而言不會是一個問題,不過對於向用戶眾多的互聯網系統來講這不是一個簡單的問題。下面是翻譯的最近最火爆的Instagram應用開發者的一篇文章,看看他們一個十幾個人的公司是怎么解決這個問題的:

 

以下為簡單翻譯(不清楚的地方請參照原文):

                                                     Instagram 的分片和IDs

       每秒接收25副圖片、90次"like"分享,Instagram存儲了大量的數據。為了確保所有重要的數據都存入到了內存並且盡快地對於用戶可用,我們將數據進行了分片---換句話說就是將數據存到很多小分片上,每個分片都持有數據的一部分。

       我們使用Django 和PostgreSQL 作為后台的數據庫系統。在決定對數據進行分片后我們遇到的第一個問題就是是否仍舊將PostgreSQL作為我們主要的數據存儲系統,還是換個其他的。我們評估了一些不同的NoSQL解決方案,但最終決定:最符合我們需求的是將數據分片到由多個PostgreSQL組成的服務器組上。

       在將數據寫入到PostgreSQL服務器組之前,我們必須先解決如何為數據庫中每一份數據指定相應的唯一標示(例如每一副發布在我們系統上的圖片)。典型的解決方案在單個數據庫中還行得通---直接使用數據庫的自增來分配唯一標示;但要將數據同時插入到多個數據庫時這種方案就不行了。這篇文章接下來的內容就指明了我們是如何對付這個問題的。

       在開始之前我們列出了幾個系統中必須的幾個功能:

       1.生成的ID必須可以按時間排序(這樣一來,一組圖片可以不用再查找其他相關信息就能排序)

       2.ID最好是64bit的(為了索引更小且方便存儲在像Redis這樣的系統中)

       3.新系統造成的不確定性(or改動)越小越好---我們之所以能用這么少的工程師搞定Instagram,很大的原因就在於選擇簡單、易懂、可靠的解決方案。

       現存的解決方案

       對於ID生成問題現在已經有很多現存的方案,以下為幾個我們可以考慮的:

 

       在web應用(web application)中生成ID

       這種方式將ID生成的問題全部交給你的應用程序,而不是數據庫來解決。

       例如:MongoDB 的ObjectID 一共有12bytes長,並且將編碼后的時間戳作為首要的組件。另一個常用的就是UUID。

       優點:

             1.每個應用程序線程都獨立地生成ID,減少了故障和不一致。

             2.如果你使用時間戳作為ID的首要組件,ID就是可以按時間排序的。

       缺點:

             1.為了保證唯一性的可靠,一般需要更多的存儲空間(96bit或更多)。

             2.有些UUID完全是隨機的,不能自然排序。

       

      使用專門的服務生成ID

      例如:Twitter的 Snowflake---一個使用Apache ZooKeeper來整合所有節點然后生成64bit唯一ID的簡潔的服務。

      優點:

            1.Snowflake生成的ID只有64位,只有UUID的一半大小。

            2.可以使用時間戳作為首要組件,可排序。

            3.分布式的系統,某個節點down掉也沒事。

      缺點:

            1.會給整體架構引入額外的復雜性,和一些不確定內容(ZooKeeper, Snowflake servers)

 

      數據庫"票務"服務器

      使用數據庫的自增功能來保證唯一性。Flickr 使用了這種方法---但使用了兩台數據庫服務器(一台生成奇數,一台生成偶數)來防止單點當機。

      優點:

            1.數據庫非常熟悉,有很多可預見的因素。

      缺點:

            1.會最終成為一個寫瓶頸(根據Flickr的報告,即使在大規模並發的情況下這也不會成為一個問題)

            2.對於管理員而言額外多了一對機器。

            3.如果使用單個數據庫則容易出現單點故障,使用多個則無法保證可以按時間排序。

     

      以上幾種方法中Twitter的離我們想要的最為接近,但是運行一個專門的ID服務所造成的額外復雜性卻是一個負面因素。

     替代地,我們選擇了一個概念上與之相似的方法,並將它整合到了PostgreSQL中。

 

     我們的解決方案

     我們的分片系統由上千個邏輯分片組成,而這些邏輯分片在代碼中與非常少的物理分片進行了映射。使用這種方法我們可以從很少的數據庫服務器開始,最終轉到更多的服務器:只需要將一些邏輯片從一台服務器移到另一台,中間不需要重新打包任何數據。為了易於編碼和管理我們使用Postgres的schema功能來實現。

Schemas(不是SQL 中的表的schema) 是邏輯上的一組功能。每個Postgres 數據庫可以擁有多個schema,每個schema中可以有一到多個表;表名在schema內是唯一的,在DB中可以不唯一;默認的,數據庫將所有的信息都放在一個叫"public"的schema中。

     在我們的系統中每個邏輯分片都是一個schema,每個被分片的表都存在於每個schema中。我們使用PL/PGSQL(Postgres內置的編程語言)和Postgers自身的自增函數,為每個分片中的每張表都賦予了生成ID功能。

     每個ID由以下部分組成:

     1.41bits 存儲毫秒格式的時間。

     2.13bits 表示邏輯分片ID。

     3.10bits 存儲自增序列值對1024取模后的結果,這意味着每個分片每秒可以產生1024個ID。

 

     下面進行一個測試:假設現在是2011-09-09 下午05:00,我們的業務從2011-01-01開始運行。從開始到現在已經運行了1387263000毫秒,開始構造我們的ID,我們使用左移位的方式填充左面的41位:

      id = 1387263000 << (64-41)

     接着,我們獲取即將插入的這份數據所在的分片ID,假設我們按用戶ID來進行分片,系統中一共有2000個邏輯分片;如果我們的用戶ID是31341,那么我們的分片ID就是31341 % 2000 -> 1341。我們使用這個值來填充接下來的13位:

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

     最后我們使用任意生成的自增序列值(單個schema單張表內唯一)來填充其余的位數。假設我們要插入的那張表已經生成了5000個ID了,下一個值就是5001,我們對其使用1024取模(這樣生成的數據就是10bits了),然后加上去:

     id |= (5001 % 1024)

    現在我們已經獲取到了想要的ID,接下來可以使用return關鍵字作為insert過程的一部分返回給應用程序了。

    下面是以上整個過程的PL/PGSQL代碼實現(例如:在schema實例5中):

 

 

 

CREATE  OR  REPLACE  FUNCTION insta5.next_id(OUT result  bigintAS $$
DECLARE
    our_epoch  bigint : =  1314220021721;
    seq_id  bigint;
    now_millis  bigint;
    shard_id  int : =  5;
BEGIN
     SELECT nextval( ' insta5.table_id_seq '%%  1024  INTO seq_id;

     SELECT  FLOOR(EXTRACT(EPOCH  FROM clock_timestamp())  *  1000INTO now_millis;
    result : = (now_millis  - our_epoch)  <<  23;
    result : = result  | (shard_id  <<  10);
    result : = result  | (seq_id);
END;
$$ LANGUAGE PLPGSQL;

 

 

      下面是創建數據庫表:

CREATE  TABLE insta5.our_table (
    "id"  bigint  NOT  NULL  DEFAULT insta5.next_id(),
    ...rest  of  table  schema...
)

      就這樣了!主鍵在我們的應用里面是唯一的(作為捎帶的私貨,為了易於映射含有分片ID)。我們已經將這個方法應用到了產品中,目前看來效果挺令人滿意。有興趣幫助我們解決這些問題?我們的聯系方式

      Mike Krieger, co-founder

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM