一,題記mysql
全部的業務系統,都有生成ID的需求,如訂單id,商品id,文章ID等。這個ID會是數據庫中的惟一主鍵,在它上面會創建彙集索引!git
ID生成的核心需求有兩點:github
全局惟一算法
趨勢有序sql
二,爲何要全局惟一?數據庫
著名的例子就是身份證號碼,身份證號碼確實是對人惟一的,然而一我的是能夠辦理多個身份證的,例如你身份證丟了,又從新補辦了一張,號碼不變。安全
問題來了,由於系統是按照身份證號碼作惟一主鍵的。此時,若是身份證是被盜的狀況下,你是沒有辦法在系統裏面註銷的,由於新舊2個身份證的「主鍵」都是身份證號碼。服務器
也就是說,舊的身份證仍然逍遙在外,徹底有效。這個時候,還好有一個身份證有效時間的東西,只有靠身份證有效期來辨識了。不過,這就是如今這麼多銀行,電信詐騙的由來,撿到一張身份證,去不少銀行,手機,酒店均可以使用!身份證缺少註銷機制!數據結構
因此,經驗告訴咱們。不要相信本身的直覺,業務上所謂的惟一每每都是不靠譜的,經不起時間的考研的。因此須要單獨設置一個和業務無關的主鍵,專業術語叫作代理主鍵(surrogate key)。併發
這也是爲何數據庫設計範式,惟一主鍵是第一範式!
三,爲何要趨勢有序
以mysql爲例,InnoDB引擎表是基於B+樹的索引組織表(IOT);每一個表都須要有一個彙集索引(clustered index);全部的行記錄都存儲在B+樹的葉子節點(leaf pages of the tree);基於彙集索引的增、刪、改、查的效率相對是最高的;以下圖:
若是咱們定義了主鍵(PRIMARY KEY),那麼InnoDB會選擇其做爲彙集索引;
若是沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的惟一索引做爲主鍵索引;
若是也沒有這樣的惟一索引,則InnoDB會選擇內置6字節長的ROWID做爲隱含的彙集索引(ROWID隨着行記錄的寫入而主鍵遞增,這個ROWID不像ORACLE的ROWID那樣可引用,是隱含的)。
綜上總結,若是InnoDB表的數據寫入順序能和B+樹索引的葉子節點順序一致的話,這時候存取效率是最高的,也就是下面這幾種狀況的存取效率最高
使用自增列(INT/BIGINT類型)作主鍵,這時候寫入順序是自增的,和B+數葉子節點分裂順序一致;
該表不指定自增列作主鍵,同時也沒有能夠被選爲主鍵的惟一索引(上面的條件),這時候InnoDB會選擇內置的ROWID做爲主鍵,寫入順序和ROWID增加順序一致;
除此之外,若是一個InnoDB表又沒有顯示主鍵,又有能夠被選擇爲主鍵的惟一索引,但該惟一索引可能不是遞增關係時(例如字符串、UUID、多字段聯合惟一索引的狀況),該表的存取效率就會比較差。)
這就是爲何咱們的分佈式ID必定要是趨勢遞增的!那麼在開發當中,面對這種分佈式ID需求,常見的處理方案有哪些呢?
四,數據庫自增加序列或字段
最多見的方式。利用數據庫,全數據庫惟一。
優勢:
1)簡單,代碼方便,性能能夠接受。
2)數字ID自然排序,對分頁或者須要排序的結果頗有幫助。
缺點:
1)不一樣數據庫語法和實現不一樣,數據庫遷移的時候或多數據庫版本支持的時候須要處理。
2)在單個數據庫或讀寫分離或一主多從的狀況下,只有一個主庫能夠生成。有單點故障的風險。
3)在性能達不到要求的狀況下,比較難於擴展。
4)若是碰見多個系統須要合併或者涉及到數據遷移會至關痛苦。
5)分表分庫的時候會有麻煩。
優化方案:
1)針對主庫單點,若是有多個Master庫,則每一個Master庫設置的起始數字不同,步長同樣,能夠是Master的個數。好比:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。這樣就能夠有效生成集羣中的惟一ID,也能夠大大下降ID生成數據庫操做的負載。
五,UUID
常見的方式。能夠利用數據庫也能夠利用程序生成,通常來講全球惟一。
優勢:
1)簡單,代碼方便。
2)生成ID性能很是好,基本不會有性能問題。
3)全球惟一,在碰見數據遷移,系統數據合併,或者數據庫變動等狀況下,能夠從容應對。
缺點:
1)沒有排序,沒法保證趨勢遞增。
2)UUID每每是使用字符串存儲,查詢的效率比較低。
3)存儲空間比較大,若是是海量數據庫,就須要考慮存儲量的問題。
4)傳輸數據量大
5)不可讀。
六,Redis生成ID
當使用數據庫來生成ID性能不夠要求的時候,咱們能夠嘗試使用Redis來生成ID。這主要依賴於Redis是單線程的,因此也能夠用生成全局惟一的ID。能夠用Redis的原子操做 INCR和INCRBY來實現。
可使用Redis集羣來獲取更高的吞吐量。假如一個集羣中有5臺Redis。能夠初始化每臺Redis的值分別是1,2,3,4,5,而後步長都是5。各個Redis生成的ID爲:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
這個,隨便負載到哪一個機肯定好,將來很難作修改。可是3-5臺服務器基本可以知足器上,均可以得到不一樣的ID。可是步長和初始值必定須要事先須要了。使用Redis集羣也能夠方式單點故障的問題。
另外,比較適合使用Redis來生成天天從0開始的流水號。好比訂單號=日期+當日自增加號。能夠天天在Redis中生成一個Key,使用INCR進行累加。
優勢:
1)不依賴於數據庫,靈活方便,且性能優於數據庫。
2)數字ID自然排序,對分頁或者須要排序的結果頗有幫助。
缺點:
1)若是系統中沒有Redis,還須要引入新的組件,增長系統複雜度。
2)須要編碼和配置的工做量比較大。
七,twitter(雪花算法)
twitter在把存儲系統從MySQL遷移到Cassandra的過程當中因爲Cassandra沒有順序ID生成機制,因而本身開發了一套全局惟一ID生成服務:Snowflake。
1 41位的時間序列(精確到毫秒,41位的長度可使用69年)
2 10位的機器標識(10位的長度最多支持部署1024個節點)
3 12位的計數順序號(12位的計數順序號支持每一個節點每毫秒產生4096個ID序號) 最高位是符號位,始終爲0。
優勢:
高性能,低延遲;獨立的應用;
按時間有序。
缺點:
須要獨立的開發和部署。
強依賴時鐘,若是主機時間回撥,則會形成重複ID,會產生
ID雖然有序,可是不連續
原理
八,MongoDB的ObjectId
MongoDB的ObjectId和snowflake算法相似。它設計成輕量型的,不一樣的機器都能用全局惟一的同種方法方便地生成它。MongoDB 從一開始就設計用來做爲分佈式數據庫,處理多個節點是一個核心要求。使其在分片環境中要容易生成得多。
ObjectId使用12字節的存儲空間,其生成方式以下:
|0|1|2|3|4|5|6 |7|8|9|10|11|
|時間戳 |機器ID|PID|計數器 |
前四個字節時間戳是從標準紀元開始的時間戳,單位爲秒,有以下特性:
1 時間戳與後邊5個字節一塊,保證秒級別的惟一性;
2 保證插入順序大體按時間排序;
3 隱含了文檔建立時間;
4 時間戳的實際值並不重要,不須要對服務器之間的時間進行同步(由於加上機器ID和進程ID已保證此值惟一,惟一性是ObjectId的最終訴求)。
機器ID是服務器主機標識,一般是機器主機名的散列值。
同一臺機器上能夠運行多個mongod實例,所以也須要加入進程標識符PID。
前9個字節保證了同一秒鐘不一樣機器不一樣進程產生的ObjectId的惟一性。後三個字節是一個自動增長的計數器(一個mongod進程須要一個全局的計數器),保證同一秒的ObjectId是惟一的。同一秒鐘最多容許每一個進程擁有(256^3 = 16777216)個不一樣的ObjectId。
總結一下:時間戳保證秒級惟一,機器ID保證設計時考慮分佈式,避免時鐘同步,PID保證同一臺服務器運行多個mongod實例時的惟一性,最後的計數器保證同一秒內的惟一性(選用幾個字節既要考慮存儲的經濟性,也要考慮併發性能的上限)。
"_id"既能夠在服務器端生成也能夠在客戶端生成,在客戶端生成能夠下降服務器端的壓力。
九,類snowflake算法(雪花算法)
國內有不少廠家基於snowflake算法進行了國產化,例如
百度的uid-generator:
https://github.com/baidu/uid-generator
美團Leaf:
https://github.com/zhuzhong/idleaf
基本是對snowflake的進一步優化,好比解決時鐘 回撥問題!
十,總結
整體而言,分佈式惟一ID須要知足如下條件:
高可用性:不能有單點故障。
全局惟一性:不能出現重複的ID號,既然是惟一標識,這是最基本的要求。
趨勢遞增:在MySQL InnoDB引擎中使用的是彙集索引,因爲多數RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上面咱們應該儘可能使用有序的主鍵保證寫入性能。
時間有序:以時間爲序,或者ID裏包含時間。這樣一是能夠少一個索引,二是冷熱數據容易分離。
分片支持:能夠控制ShardingId。好比某一個用戶的文章要放在同一個分片內,這樣查詢效率高,修改也容易。
單調遞增:保證下一個ID必定大於上一個ID,例如事務版本號、IM增量消息、排序等特殊需求。
長度適中:不要太長,最好64bit。使用long比較好操做,若是是96bit,那就要各類移位至關的不方便,還有可能有些組件不能支持這麼大的ID。
信息安全:若是ID是連續的,惡意用戶的扒取工做就很是容易作了,直接按照順序下載指定URL便可;若是是訂單號就更危險了,競爭對手能夠直接知道咱們一天的單量。因此在一些應用場景下,會須要ID無規則、不規則。
另外:阿里的訂單生成策略: 年月日+隨機數+時間戳+應用名和線程id