縮小版雪花算法的實現與多線程併發測試

我們設計數據表的時候,一般都會有ID字段,ID的生成方法有多種,比如數據庫自增,UUID,雪花算法等。

考慮到以後數據的增長,分庫分表,分佈式等要求,我們選擇雪花算法來生成ID。

開發Web系統需要後端跟前端交互,前端JavaScript支持的最大整型是53位,超過53位會丟失精度,原版的雪花算法會超過53位,我們使用縮小版的雪花算法,把位數減小到53位。

原版Snowflake算法的極限是每毫秒的每一個節點生成4059個id值,也就是說每毫秒的極限是生成023*4059=4 152 357個id值 ,縮小後極限是每秒生成15*131071=1 966 065個分佈式id,夠我們在開發裏面的日常使用了

package cn.gintone.asso.util;

/**
 * @description:縮小版的雪花算法
 * @author:Elon He
 * @create:2020-10-06
 */
public class SnowflakeMini {
    /**
     * 開始時間截 (1970-01-01)
     */
    private final static long twepoch = 0L;
    /**
     * 機器id,範圍是1到15
     */
    private final static long workerId =1L;
    /**
     * 機器id所佔的位數,佔4位
     */
    private final static long workerIdBits = 4L;
    /**
     * 支持的最大機器id,結果是15
     */
    private final static long maxWorkerId = ~(-1L << workerIdBits);
    /**
     * 生成序列佔的位數
     */
    private final static long sequenceBits = 15L;
    /**
     * 機器ID向左移15位
     */
    private final static long workerIdShift = sequenceBits;
    /**
     * 生成序列的掩碼,這裏爲最大是32767 (1111111111111=32767)
     */
    private final static long sequenceMask = ~(-1L << sequenceBits);
    /**
     * 時間截向左移19位(4+15)
     */
    private final static long timestampLeftShift = 19L;
    /**
     * 秒內序列(0~32767)
     */
    private static long sequence = 0L;
    /**
     * 上次生成ID的時間截
     */
    private static long lastTimestamp = -1L;

    /**
     * 獲得下一個ID (該方法是線程安全的)
     *
     * @return SnowflakeId
     */
    public static synchronized long nextId() {
        //返回以秒爲單位的當前時間
        long timestamp = timeGen();
        //如果當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過這個時候應當拋出異常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        //藍色代碼註釋結束
        //紅色代碼註釋開始
        //如果是同一時間生成的,則進行秒內序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //秒內序列溢出
            if (sequence == 0) {
                //阻塞到下一個秒,獲得新的秒值
                timestamp = tilNextMillis(lastTimestamp);
            }
            //時間戳改變,秒內序列重置
        }
        //紅色代碼註釋結束
        //綠色代碼註釋開始
        else {
            sequence = 0L;
        }
        //綠色代碼註釋結束
        //上次生成ID的時間截
        lastTimestamp = timestamp;
        //黃色代碼註釋開始
        //移位並通過或運算拼到一起組成53 位的ID
        return ((timestamp - twepoch) << timestampLeftShift)
                | (workerId << workerIdShift)
                | sequence;
        //黃色代碼註釋結束
    }
    /**
     * 阻塞到下一個秒,直到獲得新的時間戳
     *
     * @param lastTimestamp 上次生成ID的時間截
     * @return 當前時間戳
     */
    protected static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    /**
     * 返回以秒爲單位的當前時間
     *
     * @return 當前時間(秒)
     */
    protected static long timeGen() {
        return System.currentTimeMillis()/1000L;
    }

}

創建兩個線程,同時測試ID獲取。
測試線程類A

package cn.gintone.asso;


import cn.gintone.asso.util.SnowflakeMini;

/**
 * @description:線程A
 * @author:Elon He
 * @create:2020-10-06
 */
public class ThreadA extends Thread{

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            long id = SnowflakeMini.nextId();
            System.out.println("A:"+id);
        }
    }
}

測試線程類B

package cn.gintone.asso;

import cn.gintone.asso.util.SnowflakeMini;

/**
 * @description:線程B
 * @author:Elon He
 * @create:2020-10-06
 */
public class ThreadB extends  Thread{

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            long id = SnowflakeMini.nextId();
            System.out.println("B:"+id);
        }
    }
}

測試類(註釋掉的代碼是修改成靜態方法前的測試方法)

package cn.gintone.asso;

/**
 * @description:雪花算法測試類
 * @author:Elon He
 * @create:2020-10-06
 */
public class TestSnowflake {
    public static void main(String[] args) {
        //不同線程使用同一個對象,不重複
//        SnowflakeMini idWorker = new SnowflakeMini(0);
//        ThreadA t1 = new ThreadA(idWorker);
//        ThreadB t2 = new ThreadB(idWorker);
//        t1.start();
//        t2.start();
        //不同線程使用不同對象,會重複
//        SnowflakeMini idWorker1 = new SnowflakeMini(0);
//        SnowflakeMini idWorker2 = new SnowflakeMini(0);
//        ThreadA t1 = new ThreadA(idWorker1);
//        ThreadB t2 = new ThreadB(idWorker2);
//        t1.start();
//        t2.start();

        //nextId修改成靜態方法後測試,不重複
        ThreadA t1 = new ThreadA();
        ThreadB t2 = new ThreadB();
        t1.start();
        t2.start();

    }
}

測試結果

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