分散式唯一id生成策略

黑色鞋幫發表於2018-11-26

最近發現公司用的公共jar包裡生成唯一主鍵的方法竟然用的是當前時間戳,這種方式有明顯弊端,參考了網上各種生成唯一id的方式之後,做下總結。

一、資料庫自增ID

使用mysql資料庫的自增id,資料庫的自增id的優點是非常明顯的:第一是速度快,而且是按序自增,檢索非常有利。第二是自增的id都是數字型,佔用空間小,易於程式中排序。 第三是新增記錄時可以不用指定id,不用擔心主鍵重複問題。

當然,使用資料庫自增ID也有缺點:第一是手動插入指定ID的記錄時會比較麻煩。第二是當新老系統合併做資料遷移時,新舊系統都是數字型,會導致多個主鍵id衝突,或新舊系統主鍵不同是數字型就會導致修改主鍵資料型別。第三如果ID是連續的,惡意使用者的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道我們一天的單量。所以在一些應用場景下,會需要ID無規則、不規則。

二、UUID

UUID保證對在同一時空中的所有機器都是唯一的。UUID由以下幾部分的組合:
(1)當前日期和時間,UUID的第一個部分與時間有關,如果你在生成一個UUID之後,過幾秒又生成一個UUID,則第一個部分不同,其餘相同。
(2)時鐘序列。
(3)全域性唯一的IEEE機器識別號,如果有網路卡,從網路卡MAC地址獲得,沒有網路卡以其他方式獲得。

java中提供了兩個對應的方法:

randomUUID() ,適用於生成唯一訂單號。 

nameUUIDFromBytes(byte[] n)會根據n產生唯一的uuid。只要有使用者的唯一性資訊。就能保證此使用者的uuid的唯一性。

優點:效能非常高:本地生成,沒有網路消耗。

缺點:第一是不易儲存,UUID太長,16位元組128位,通常以36長度的字串表示,很多場景不適用。第二是資訊不安全,基於MAC地址生成UUID的演算法可能會造成MAC地址洩露,這個漏洞曾被用於尋找梅麗莎病毒的製作者位置。

下面是UUID及生成8位UUID的java例項:

public class UUIDTest {

    public static String[] chars = new String[]
            {
                    "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
                    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
                    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
            };

    public static void main(String[] args) {
        System.out.println(UUID.randomUUID().toString().replace("-", ""));
        System.out.println(getShortUuid());
    }

    public static String getShortUuid() {
        StringBuffer stringBuffer = new StringBuffer();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        for (int i = 0; i < 8; i++) {
            String str = uuid.substring(i * 4, i * 4 + 4);
            int strInteger = Integer.parseInt(str, 16);
            stringBuffer.append(chars[strInteger % 0x3E]);
        }

        return stringBuffer.toString();
    }
}複製程式碼

其中生成8位UUID的思路是:將32位UUID按每四位擷取,將擷取的每組字串轉換成10進位制的數字,然後對0x3E(十進位制為62)取餘,根據取餘得到的結果找到ASCII字元陣列中(總共62個字元)的位置,將每組的結果拼接就得到一個8位的UUID字串。

三、SnowFlake雪花演算法

雪花ID生成的是一個64位的二進位制正整數,然後轉換成10進位制的數。64位二進位制數由如下部分組成:

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

1位標識部分,在java中由於long的最高位是符號位,正數是0,負數是1,一般生成的ID為正數,所以為0;
41位時間戳部分,這個是毫秒級的時間,一般實現上不會儲存當前的時間戳,而是時間戳的差值(當前時間-固定的開始時間),這樣可以使產生的ID從更小值開始;41位的時間戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年;
10位節點部分,Twitter實現中使用前5位作為資料中心標識,後5位作為機器標識,可以部署1024個節點;
12位序列號部分,支援同一毫秒內同一個節點可以生成4096個ID;

優點

  • 簡單高效,生成速度快。
  • 時間戳在高位,自增序列在低位,整個ID是趨勢遞增的,按照時間有序遞增。
  • 靈活度高,可以根據業務需求,調整bit位的劃分,滿足不同的需求。

缺點

  • 依賴機器的時鐘,如果伺服器時鐘回撥,會導致重複ID生成。
  • 在分散式環境上,每個伺服器的時鐘不可能完全同步,有時會出現不是全域性遞增的情況。


相關文章