java算法(4)---靜態內部類實現雪花算法

#<center> 靜態內部類單例模式實現雪花算法 </center>html

在生成表主鍵ID時,咱們能夠考慮主鍵自增 或者 UUID,但它們都有很明顯的缺點java

主鍵自增一、自增ID容易被爬蟲遍歷數據。二、分表分庫會有ID衝突。算法

UUID: 一、太長,而且有索引碎片,索引多佔用空間的問題 二、無序。安全

雪花算法就很適合在分佈式場景下生成惟一ID,它既能夠保證惟一又能夠排序。爲了提升生產雪花ID的效率,服務器

在這裏面數據的運算都採用的是位運算,若是對位運算不了解能夠參考博客:【java提升】(17)---Java 位運算符併發

<font color=#FFD700> 1、概念</font>

一、原理

SnowFlake算法生成ID的結果是一個64bit大小的整數,它的結構以下圖:分佈式

算法描述:函數

  • 1bit 由於二進制中最高位是符號位,1表示負數,0表示正數。生成的ID都是正整數,因此最高位固定爲0。性能

  • 41bit-時間戳 精確到毫秒級,41位的長度可使用69年。時間位還有一個很重要的做用是能夠根據時間進行排序。測試

  • 10bit-工做機器id 10位的機器標識,10位的長度最多支持部署1024個節點。

  • 12bit-序列號 序列號即一系列的自增id,能夠支持同一節點同一毫秒生成多個ID序號。 12位(bit)能夠表示的最大正整數是2^{12}-1 = 4095,便可以用0、一、二、三、....4094這4095個數字,來表示同一機器同一時間截(毫秒)內產生的4095個ID序號。

說明 因爲在Java中64bit的整數是long類型,因此在Java中SnowFlake算法生成的id就是long來存儲的。 <br>

<font color=#FFD700> 2、靜態類部類單例模式生產雪花ID代碼</font>

下面生成雪花ID的代碼能夠用於線上分佈式項目中來生成分佈式主鍵ID,由於設計採用的靜態內部類的單例模式,經過加synchronized鎖來保證在

同一個服務器線程安全。至於不一樣服務器實際上是不相關的,由於它們的機器碼是不一致的,因此就算同一時刻兩臺服務器都產生了雪花ID,那也不會同樣的。

一、代碼

/**
 * @author xub
 * @Description: 雪花算法
 * @date 2019/8/14 下午8:22
 */
@Slf4j
public class SnowIdUtils {
    /**
     * 私有的 靜態內部類
     */
    private static class SnowFlake {

        /**
         * 內部類對象(單例模式)
         */
        private static final SnowIdUtils.SnowFlake SNOW_FLAKE = new SnowIdUtils.SnowFlake();
        /**
         * 起始的時間戳
         */
        private final long START_TIMESTAMP = 1557489395327L;
        /**
         * 序列號佔用位數
         */
        private final long SEQUENCE_BIT = 12;
        /**
         * 機器標識佔用位數
         */
        private final long MACHINE_BIT = 10;
        /**
         * 時間戳位移位數
         */
        private final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;
        /**
         * 最大序列號  (4095)
         */
        private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
        /**
         * 最大機器編號 (1023)
         */
        private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);
        /**
         * 生成id機器標識部分
         */
        private long machineIdPart;
        /**
         * 序列號
         */
        private long sequence = 0L;
        /**
         * 上一次時間戳
         */
        private long lastStamp = -1L;

        /**
         * 構造函數初始化機器編碼
         */
        private SnowFlake() {
            //模擬這裏得到本機機器編碼
            long localIp = 4321;
            //localIp & MAX_MACHINE_ID最大不會超過1023,在左位移12位
            machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT;
        }
        /**
         * 獲取雪花ID
         */
        public synchronized long nextId() {
            long currentStamp = timeGen();
            //避免機器時鐘回撥
            while (currentStamp < lastStamp) {
                // //服務器時鐘被調整了,ID生成器中止服務.
                throw new RuntimeException(String.format("時鐘已經回撥.  Refusing to generate id for %d milliseconds", lastStamp - currentStamp));
            }
            if (currentStamp == lastStamp) {
                // 每次+1
                sequence = (sequence + 1) & MAX_SEQUENCE;
                // 毫秒內序列溢出
                if (sequence == 0) {
                    // 阻塞到下一個毫秒,得到新的時間戳
                    currentStamp = getNextMill();
                }
            } else {
                //不一樣毫秒內,序列號置0
                sequence = 0L;
            }
            lastStamp = currentStamp;
            //時間戳部分+機器標識部分+序列號部分
            return (currentStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | machineIdPart | sequence;
        }
        /**
         * 阻塞到下一個毫秒,直到得到新的時間戳
         */
        private long getNextMill() {
            long mill = timeGen();
            //
            while (mill <= lastStamp) {
                mill = timeGen();
            }
            return mill;
        }
        /**
         * 返回以毫秒爲單位的當前時間
         */
        protected long timeGen() {
            return System.currentTimeMillis();
        }
    }

    /**
     * 獲取long類型雪花ID
     */
    public static long uniqueLong() {
        return SnowIdUtils.SnowFlake.SNOW_FLAKE.nextId();
    }
    /**
     * 獲取String類型雪花ID
     */
    public static String uniqueLongHex() {
        return String.format("%016x", uniqueLong());
    }

    /**
     * 測試
     */
    public static void main(String[] args) throws InterruptedException {
        //計時開始時間
        long start = System.currentTimeMillis();
        //讓100個線程同時進行
        final CountDownLatch latch = new CountDownLatch(100);
        //判斷生成的20萬條記錄是否有重複記錄
        final Map<Long, Integer> map = new ConcurrentHashMap();
        for (int i = 0; i < 100; i++) {
            //建立100個線程
            new Thread(() -> {
                for (int s = 0; s < 2000; s++) {
                    long snowID = SnowIdUtils.uniqueLong();
                    log.info("生成雪花ID={}",snowID);
                    Integer put = map.put(snowID, 1);
                    if (put != null) {
                        throw new RuntimeException("主鍵重複");
                    }
                }
                latch.countDown();
            }).start();
        }
        //讓上面100個線程執行結束後,在走下面輸出信息
        latch.await();
        log.info("生成20萬條雪花ID總用時={}", System.currentTimeMillis() - start);
    }
}

二、測試結果

從圖中咱們能夠得出

一、在100個線程併發下,生成20萬條雪花ID的時間大概在1.6秒左右,全部所性能仍是蠻ok的。

二、生成20萬條雪花ID並無一條相同的ID,由於有一條就會拋出異常了。

三、爲何說41位時間戳最長只能有69年

咱們思考41的二進制,最大值也就41位都是1,也就是也就是說41位能夠表示2^{41}-1個毫秒的值,轉化成單位年則是

(2^{41}-1) / (1000 * 60 * 60 * 24 *365) = 69

咱們能夠經過代碼泡一下就知道了。

public static void main(String[] args) {
        //41位二進制最小值
        String minTimeStampStr = "00000000000000000000000000000000000000000";
        //41位二進制最大值
        String maxTimeStampStr = "11111111111111111111111111111111111111111";
        //轉10進制
        long minTimeStamp = new BigInteger(minTimeStampStr, 2).longValue();
        long maxTimeStamp = new BigInteger(maxTimeStampStr, 2).longValue();
        //一年總共多少毫秒
        long oneYearMills = 1L * 1000 * 60 * 60 * 24 * 365;
        //算出最大能夠多少年
        System.out.println((maxTimeStamp - minTimeStamp) / oneYearMills);
    }

運行結果

因此說雪花算法生成的ID,只能保證69年內不會重複,若是超過69年的話,那就考慮換個服務器部署吧,而且要保證該服務器的ID和以前都沒有重複過。 <br> <br>

我相信,不管從此的道路多麼坎坷,只要抓住今天,早晚會在奮鬥中嚐到人生的甘甜。抓住人生中的一分一秒,賽過虛度中的一月一年!(5)

<br>

原文出處:https://www.cnblogs.com/qdhxhz/p/11372658.html

相關文章
相關標籤/搜索