分散式系統全域性唯一Id(SnowFlake)雪花演算法實現

rumenxiaobaidog發表於2020-12-15

簡介

由於工作中需要提供通用的唯一ID生成方案,調研了目前市面上通用的解決方案,最終經過彙總,解決了(SnowFlake)雪花演算法的坑,時鐘回撥問題

其中最主要的就是  處理時鐘回撥  ,生成的規則是傳入伺服器的唯一workId,根據workId生成id

 

程式碼

package com.example.demo;

/**
 * @Author: rumenxiaobaidog
 * @Date: 2020/12/15
 * @Desc: 雪花演算法生成唯一id  處理了時鐘回撥的坑
 */
public class SnowFlakeWorkerUtils {
    private volatile static SnowFlakeWorkerUtils snowFlakeWorkerInstance;

    // 1位標識部分    -      41位時間戳部分        -         10位節點部分     12位序列號部分
    /** 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 */
    /**
     * 起始的時間戳
     */
    private final static long START_STMP = 1288834974657L;

    /**
     * 每一部分佔用的位數
     */
    // 序列號佔用的位數
    private final static long SEQUENCE_BIT = 12;
    // 機器標識佔用的位數
    private final static long WORK_BIT = 10;

    /**
     * WORK_NUM最大值 1023
     */
    private final static long MAX_WORK_NUM = -1L ^ (-1L << WORK_BIT);
    /**
     * SEQUENCE最大值 4095
     */
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long WORK_LEFT = SEQUENCE_BIT;
    private final static long TIMESTMP_LEFT = WORK_LEFT + WORK_BIT;

    private long workId;
    private long sequence = 0L;  //序列號
    private long lastStmp = -1L; //上一次時間戳

    /** 步長, 1024 */
    private static long stepSize = 2 << 9;
    /** 基礎序列號, 每發生一次時鐘回撥即改變, basicSequence += stepSize */
    private long basicSequence = 0L;

    private SnowFlakeWorkerUtils(long workId) {
        if (workId > MAX_WORK_NUM || workId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        this.workId = workId;
    }


    private synchronized static SnowFlakeWorkerUtils initSnowFlakeWorker(long workId) {
        snowFlakeWorkerInstance = new SnowFlakeWorkerUtils(workId);
        return snowFlakeWorkerInstance;
    }

    public static SnowFlakeWorkerUtils getInstance(long workId) {
        if (null == snowFlakeWorkerInstance) {
            synchronized (SnowFlakeWorkerUtils.class) {
                if (null == snowFlakeWorkerInstance) {
                    snowFlakeWorkerInstance = new SnowFlakeWorkerUtils(workId);
                }
            }
        }
        return snowFlakeWorkerInstance;
    }


    /**
     * 產生下一個ID
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            return handleClockBackwards(currStmp);
        }

        if (currStmp == lastStmp) {
            // 相同毫秒內,序列號自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列數已經達到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            // 不同毫秒內,序列號置為 basicSequence
            sequence = basicSequence;
        }

        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT  // 時間戳部分
                | workId << WORK_LEFT                    // 節點部分
                | sequence;                              // 序列號部分
    }

    /**
     * 處理時鐘回撥
     */
    private long handleClockBackwards(long currStmp) {
        basicSequence += stepSize;
        if (basicSequence == MAX_SEQUENCE + 1) {
            basicSequence = 0;
            currStmp = getNextMill();
        }
        sequence = basicSequence;

        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT  // 時間戳部分
                | workId << WORK_LEFT                    // 節點部分
                | sequence;                              // 序列號部分
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        //workId 可根據不同的機器標識唯一的id
        Long workId = 1L;
        for (int i = 0; i < 100; i++) {
            long nextId = SnowFlakeWorkerUtils.getInstance(workId).nextId();
            System.out.println(nextId);
        }
    }

}

基於github開源的SnowFlake加以改進,處理了時鐘回撥

特別感謝:   https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java

希望給大家提供幫助,謝謝

 

相關文章