1. 程式人生 > >分散式儲存中,生成全域性唯一ID的幾種方案

分散式儲存中,生成全域性唯一ID的幾種方案

1.自定義生成規則
    eg:
      3位伺服器編碼+15位年月日時分秒毫秒+3位表編碼+4位隨機碼  (這樣就完全單機完成編碼任務)---共25位
      3位伺服器編碼+15位年月日時分秒毫秒+3位表編碼+4流水碼  (這樣流水碼就需要結合資料庫和快取)---共25位
2.單獨開一個數據庫,獲取全域性唯一的自增序列或個表的MaxId
      eg:
        Flickr 方案  --- http://blog.csdn.net/taotao4/article/details/46520053
        replace into + 奇偶2個數據庫主鍵生成伺服器(防止單點故障)

資料在分片時,典型的是分庫分表,就有一個全域性ID生成的問題。
單純的生成全域性ID並不是什麼難題,但是生成的ID通常要滿足分片的一些要求:
   1 不能有單點故障。
   2 以時間為序,或者ID裡包含時間。這樣一是可以少一個索引,二是冷熱資料容易分離。
   3 可以控制ShardingId。比如某一個使用者的文章要放在同一個分片內,這樣查詢效率高,修改也容易。
   4 不要太長,最好64bit。使用long比較好操作,如果是96bit,那就要各種移位相當的不方便,還有可能有些元件不能支援這麼大的ID。

一 twitter 
twitter在把儲存系統從MySQL遷移到Cassandra的過程中由於Cassandra沒有順序ID生成機制,於是自己開發了一套全域性唯一ID生成服務:Snowflake。
1 41位的時間序列(精確到毫秒,41位的長度可以使用69年)
2 10位的機器標識(10位的長度最多支援部署1024個節點) 
3 12位的計數順序號(12位的計數順序號支援每個節點每毫秒產生4096個ID序號) 最高位是符號位,始終為0。
優點:

高效能,低延遲;獨立的應用;按時間有序。 缺點:需要獨立的開發和部署。

原理

時間戳

這裡時間戳的細度是毫秒級,具體程式碼如下,建議使用64位linux系統機器,因為有vdso,gettimeofday()在使用者態就可以完成操作,減少了進入核心態的損耗。

uint64_t generateStamp()
{
    timeval tv;
    gettimeofday(&tv, 0);
    return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
}

預設情況下有41個bit可以供使用,那麼一共有T(1llu << 41)毫秒供你使用分配,年份 = T / (3600 * 24 * 365 * 1000) = 69.7年。如果你只給時間戳分配39個bit使用,那麼根據同樣的演算法最後年份 = 17.4年。

工作機器ID

嚴格意義上來說這個bit段的使用可以是程序級,機器級的話你可以使用MAC地址來唯一標示工作機器,工作程序級可以使用IP+Path來區分工作程序。如果工作機器比較少,可以使用配置檔案來設定這個id是一個不錯的選擇,如果機器過多配置檔案的維護是一個災難性的事情。

這裡的解決方案是需要一個工作id分配的程序,可以使用自己編寫一個簡單程序來記錄分配id,或者利用Mysql auto_increment機制也可以達到效果。

工作程序與工作id分配器只是在工作程序啟動的時候互動一次,然後工作程序可以自行將分配的id資料落檔案,下一次啟動直接讀取檔案裡的id使用。

PS:這個工作機器id的bit段也可以進一步拆分,比如用前5個bit標記程序id,後5個bit標記執行緒id之類:D

序列號

序列號就是一系列的自增id(多執行緒建議使用atomic),為了處理在同一毫秒內需要給多條訊息分配id,若同一毫秒把序列號用完了,則“等待至下一毫秒”。

uint64_t waitNextMs(uint64_t lastStamp)
{
    uint64_t cur = 0;
    do {
        cur = generateStamp();
    } while (cur <= lastStamp);
    return cur;
}

總體來說,是一個很高效很方便的GUID產生演算法,一個int64_t欄位就可以勝任,不像現在主流128bit的GUID演算法,即使無法保證嚴格的id序列性,但是對於特定的業務,比如用做遊戲伺服器端的GUID產生會很方便。另外,在多執行緒的環境下,序列號使用atomic可以在程式碼實現上有效減少鎖的密度。

最高位是符號位,始終為0。

優點:高效能,低延遲;獨立的應用;按時間有序。

缺點:需要獨立的開發和部署。


java 實現程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class IdWorker { private final long workerId; private final static long twepoch = 1288834974657L;