分佈式環境生成唯一訂單id方案

分佈式環境生成一個唯一id從來不是一個容易的事,不同的節點都獨立的各自生成id,高併發性場景下容易生成相同的訂單id。


方案1:數據庫自增主鍵

優點:全局唯一、不會重複

缺點:訂單id有序、容易被外界爬蟲知道業務的訂單量數據


方案2:UUID

UUID(Universally Unique Identifier)的標準型式包含32個16進制數字,以連字號分爲五段,形式爲8-4-4-4-12的36個字符,示例:550e8400-e29b-41d4-a716-446655440000

優點:性能非常高,本地生成,沒有網絡消耗

缺點:不易於存儲,UUID太長,16字節128位,通常以36長度的字符串表示

           由於是無序,長字符串,存儲數據庫後索引效率比較差

String uuid = UUID.randomUUID().toString()

方案3:時間戳 + ip或者Mac地址

優點:無序、高併發情況下幾乎不可能產生相同id,大部分業務可以滿足使用

缺點:不適用超高併發,例如1ms可以生成多個訂單id的場景,對系統健壯性要求極高的場景


方案4:snowflake(推薦)

Snowflake,Twitter開源的一種分佈式ID生成算法。基於64位數實現,下圖爲Snowflake算法的ID構成圖。

 

整個64-bit由以下組成部分組成

1.第一位

佔用1bit,其值始終是0,沒有實際作用。

2.時間戳

佔用41bit,精確到毫秒,總共可以容納約69 年的時間。

3.工作機器id

佔用10bit,其中高位5bit是數據中心ID(datacenterId),低位5bit是工作節點ID(workerId),最多可以容納1024個節點。

4.序列號

佔用12bit,這個值在同一毫秒同一節點上從0開始不斷累加,最多可以累加到4095。

SnowFlake算法在同一毫秒的ID數量 = 1024 X 4096 = 4194304 ,一毫秒可以生成419萬的訂單id !!!

 

用Java實現SnowFlake算法 , 其實Snowflake算法不難,設計的也很巧妙

/**
 * Created by Zhon.Thao on 2019/10/24.
 *
 * @author Zhon.Thao
 */
public class SnowFlake {

    /**
     * 起始的時間戳:這個時間戳自己隨意獲取
     */
    private final static long START_MILLS = 1543903501000L;

    /**
     * 每一部分佔用的位數
     */
    private final static long SEQUENCE_BIT = 12; //序列號佔用的位數

    /**
     * 機器標識佔用的位數
     */
    private final static long MACHINE_BIT = 5;

    /**
     * 數據中心佔用的位數
     */
    private final static long DATACENTER_BIT = 5;

    /**
     * 用位運算計算出最大支持的數據中心數量:31
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);

    /**
     * 用位運算計算出最大支持的機器數量:31
     */
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);

    /**
     * 用位運算計算出12位能存儲的最大正整數:4095
     */
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 機器標誌較序列號的偏移量
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;

    /**
     * 數據中心較機器標誌的偏移量
     */
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    /**
     * 時間戳較數據中心的偏移量
     */
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    /**
     * 數據中心id
     */
    private long dataCenterId;

    /**
     * 機器標識id
     */
    private long machineId;

    /**
     * 序列號 1ms內
     */
    private static long sequence = 0L;

    /**
     * 上一次的時間戳
     */
    private static long lastMills = -1L;

    /**
     * 如果業務方有datacenterId、machineId使用以下構造方法
     */
    public SnowFlake(long dataCenterId, long machineId) {
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }

    /**
     * 如果業務方覺得配置datacenterId、machineId比較麻煩,可以各自機器隨機從最大值裏去一個數
     * * 在極高併發下可能會生成相同id
     */
    public SnowFlake() {
        this.dataCenterId = RandomUtils.nextLong(0, MAX_DATACENTER_NUM);
        this.machineId = RandomUtils.nextLong(0, MAX_MACHINE_NUM);
    }

    /**
     * 生成訂單ID
     *
     * @return
     */
    public synchronized long getId() {

        /** 獲取當前時間戳 */
        long currMills = System.currentTimeMillis();

        /** 如果當前時間戳小於上次時間戳則拋出異常 */
        if (currMills < lastMills) {
            throw new RuntimeException("getId error,currMills < lastMills");
        }

        /** 相同毫秒內 */
        if (currMills == lastMills) {
            //相同毫秒內,序列號自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列數已經達到最大
            if (sequence == 0L) {

                /** 獲取下一時間的時間戳並賦值給當前時間戳 */
                currMills = getNextMill();
            }
        } else {
            //不同毫秒內,序列號置爲0
            sequence = 0L;
        }

        /** 當前時間戳存檔記錄,用於下次產生id時對比是否爲相同時間戳 */
        lastMills = currMills;


        return  (currMills - START_MILLS) << TIMESTMP_LEFT //時間戳部分
                | dataCenterId << DATACENTER_LEFT      //數據中心部分
                | machineId << MACHINE_LEFT            //機器標識部分
                | sequence;                            //序列號部分
    }

    private static long getNextMill() {
        long mill = System.currentTimeMillis();
        while (mill <= lastMills) {
            mill = System.currentTimeMillis();
        }
        return mill;
    }
}

方案五 :  美團點評分佈式唯一id生成系統,目前已開源

         文檔 : https://tech.meituan.com/2017/04/21/mt-leaf.html
         Github: https://github.com/Meituan-Dianping/Leaf

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章