1. 程式人生 > >分散式應用序列號生成

分散式應用序列號生成

工程中先後考慮了幾種業務號生成的方式,最終採用 snowFlake 演算法:

以下是自己批註的一些東東,mark 一下:

相對於 引用的原文, 這裡將 SnowFlake 以單例的形式, 注入到 Spring 容器管理。

單例 和 加鎖,私以為,只有單例 和 加鎖,才能真正保證獲取 Id 的執行緒是安全地。

將單例 注入 Spring 中的方法,可以參考:

以下是原始碼

package com.sinosoft.app.common.util;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
 * 整個snowFlake演算法基於 二進位制運算
 *  snowFlake 序列號,佔位 long 整形的 64 位 二進位制
 *  第一組:前 44 位 儲存 timestamp
 *  第二組:資料中心編碼 3 位 (對應 2^3 = 8 組)
 *  第三組:機器編碼 7 位 (2^7=32臺分散式應用)
 *  第四組: 十位序列號(2^10 = 1024 ) 一毫秒 一個節點應用,支援產生 1024 個不重複的序列號,如果有重複的, 則該序列號延遲到下一個毫秒
 * @author pzj
 *
 */
@Component
public class SnowFlake {
    // 起始的時間戳
    private final static long START_STMP = 1531971653531L;
    // 每一部分佔用的位數,就三個
    private final static long SEQUENCE_BIT = 10;// 序列號佔用的位數
    private final static long MACHINE_BIT = 7; // 機器標識佔用的位數
    private final static long DATACENTER_BIT = 3;// 資料中心佔用的位數
    // 每一部分最大值
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    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;
   
  /* @Value("${datacenterId}")
    private  String datacenterIdStr; // 資料中心
    @Value("${machineId}")
    private  String  machineIdStr; // 機器標識
*/   
   @Value("${datacenterId}")
    private   long datacenterId;
    @Value("${machineId}")
    private   long  machineId;
    
    private static  SnowFlake  snowFlake= new SnowFlake();
    
    private long sequence = 0L; // 序列號
    private long lastStmp = -1L;// 上一次時間戳

    private SnowFlake() {
        if (this.datacenterId > MAX_DATACENTER_NUM || this.datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (this.machineId > MAX_MACHINE_NUM || this.machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
    }
        
    public static  SnowFlake getSnowFlake(){
         return snowFlake;
    }
    
    //產生下一個ID
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStmp == lastStmp) {
            //if條件裡表示當前呼叫和上一次呼叫落在了相同毫秒內,只能通過第三部分,序列號自增來判斷為唯一,所以+1.
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列數已經達到最大,只能等待下一個毫秒
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            //不同毫秒內,序列號置為0
            //執行到這個分支的前提是currTimestamp > lastTimestamp,說明本次呼叫跟上次呼叫對比,已經不再同一個毫秒內了,這個時候序號可以重新回置0了。
            sequence = 0L;
        }

        lastStmp = currStmp;
         //就是用相對毫秒數、機器ID和自增序號拼接
        return (currStmp - START_STMP) << TIMESTMP_LEFT //時間戳部分
                | datacenterId << DATACENTER_LEFT      //資料中心部分
                | machineId << MACHINE_LEFT            //機器標識部分
                | sequence;                            //序列號部分
    }

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

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


    @PostConstruct 
    public void init() {
        snowFlake = this;
        snowFlake.datacenterId = this.datacenterId;
        snowFlake.machineId = this.machineId;
    }
}
 

對於二進位制腦補困難的同學,可以嘗試計算器上的這個功能,很好用: