天天看點

算法系列:Twitter-Snowflake[雪花算法],64位自增ID算法詳解

今天給大家分享一篇關于雪花算法的文章,希望對你有所幫助!

Twitter-Snowflake算法産生的背景相當簡單,為了滿足Twitter每秒上萬條消息的請求,每條消息都必須配置設定一條唯一的id,這些id還需要一些大緻的順序(友善用戶端排序),并且在分布式系統中不同機器産生的id必須不同。

Snowflake算法核心

把時間戳,工作機器id,序列号組合在一起。

算法系列:Twitter-Snowflake[雪花算法],64位自增ID算法詳解

除了最高位bit标記為不可用以外,其餘三組bit占位均可浮動,看具體的業務需求而定。預設情況下41bit的時間戳可以支援該算法使用到2082年,10bit的工作機器id可以支援1023台機器,序列号支援1毫秒産生4095個自增序列id。下文會具體分析。

Snowflake – 時間戳

這裡時間戳的細度是毫秒級,具體代碼如下,建議使用64位linux系統機器,因為有vdso,gettimeofday()在使用者态就可以完成操作,減少了進入核心态的損耗。

1

2

3

4

5

6

​uint64_t generateStamp()​

​{​

​timeval tv;​

​gettimeofday(&tv, 0);​

​return​

​​ ​

​(uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;​

​}​

預設情況下有41個bit可以供使用,那麼一共有T(1llu << 41)毫秒供你使用配置設定,年份 = T / (3600 * 24 * 365 * 1000) = 69.7年。如果你隻給時間戳配置設定39個bit使用,那麼根據同樣的算法最後年份 = 17.4年。

Snowflake – 工作機器id

嚴格意義上來說這個bit段的使用可以是程序級,機器級的話你可以使用MAC位址來唯一标示工作機器,工作程序級可以使用IP+Path來區分工作程序。如果工作機器比較少,可以使用配置檔案來設定這個id是一個不錯的選擇,如果機器過多配置檔案的維護是一個災難性的事情。

這裡的解決方案是需要一個工作id配置設定的程序,可以使用自己編寫一個簡單程序來記錄配置設定id,或者利用Mysql auto_increment機制也可以達到效果。

算法系列:Twitter-Snowflake[雪花算法],64位自增ID算法詳解

工作程序與工作id配置設定器隻是在工作程序啟動的時候互動一次,然後工作程序可以自行将配置設定的id資料落檔案,下一次啟動直接讀取檔案裡的id使用。

PS:這個工作機器id的bit段也可以進一步拆分,比如用前5個bit标記程序id,後5個bit标記線程id之類:D

Snowflake – 序列号

序列号就是一系列的自增id(多線程建議使用atomic),為了處理在同一毫秒内需要給多條消息配置設定id,若同一毫秒把序列号用完了,則“等待至下一毫秒”。

1

2

3

4

5

6

7

8

​uint64_t waitNextMs(uint64_t lastStamp)​

​{​

​uint64_t cur = 0;​

​do​

​​ ​

​{​

​cur = generateStamp();​

​} ​

​​

​while​

​​ ​

​(cur <= lastStamp);​

​return​

​​ ​

​cur;​

​}​

總體來說,是一個很高效很友善的GUID産生算法,一個int64_t字段就可以勝任,不像現在主流128bit的GUID算法,即使無法保證嚴格的id序列性,但是對于特定的業務,比如用做遊戲伺服器端的GUID産生會很友善。另外,在多線程的環境下,序列号使用atomic可以在代碼實作上有效減少鎖的密度。

java版本
/**
 * Twitter的分布式自增ID雪花算法snowflake
 * @author MENG
 * @create 2018-08-23 10:21
 **/
public class SnowFlake {


    /**
     * 起始的時間戳
     */
    private final static long START_STMP = 1480166465631L;
    //private final static long START_STMP = 1480166465631L;


    /**
     * 每一部分占用的位數
     */
    private final static long SEQUENCE_BIT = 12; //序列号占用的位數
    private final static long MACHINE_BIT = 5;   //機器辨別占用的位數
    private final static long DATACENTER_BIT = 5;//資料中心占用的位數


    /**
     * 每一部分的最大值
     */
    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;


    private long datacenterId;  //資料中心
    private long machineId;     //機器辨別
    private long sequence = 0L; //序列号
    private long lastStmp = -1L;//上一次時間戳


    public SnowFlake(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }


    /**
     * 産生下一個ID
     *
     * @return
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }


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


        lastStmp = currStmp;


        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();
    }


    public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(1, 1);


        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            System.out.println(snowFlake.nextId());
        }


        System.out.println(System.currentTimeMillis() - start);




    }
}      
PHP版本:
//https://learnku.com/articles/32575
//defined('BASEPATH') or exit('No direct script access allowed');
/**
 *
 *  雪花全局唯一 ID 生成類
 *  修改了機器id和序列号自動擷取 理論上毫秒可生成 1024*4096個唯一id
 *  組成: <毫秒級時間戳+機器id+序列号>
 *  41bit的時間戳 可以支援該算法使用到2082年,
 *  10bit的機器id 可以支援1023台機器,
 *  12bit序列号   可以支援1毫秒産生4095個自增序列id
 *  
 */
class Snowflake
{
    const EPOCH    = 1479533469598; //開始時間,固定一個小于目前時間的毫秒數
    //const EPOCH    = 1479533; //開始時間,固定一個小于目前時間的毫秒數
    const max12bit = 4095;
    const max41bit = 1099511627775;
    static $LAST_TIME_MID = 0; // 上次的機器id
    static $SEQUENCE_NUM  = 0; // 
    public static function createOnlyId()
{
        // 時間戳 42位元組
        $time = floor(microtime(true) * 1000);
        // 目前時間 與 開始時間 內插補點
        $time -= self::EPOCH;
        // 二進制的 毫秒級時間戳
        $base = decbin(self::max41bit + $time);
        // 擷取序列數
        self::get_num();
        // 擷取機器id
        self::get_mid();
        // 機器id  10 位元組
        $machineid = str_pad(decbin(self::$LAST_TIME_MID), 10, "0", STR_PAD_LEFT);
        // 序列數 12位元組
        $random = str_pad(decbin(self::$SEQUENCE_NUM), 12, "0", STR_PAD_LEFT);
        // 拼接
        $base = $base . $machineid . $random;
        if (strpos(PHP_OS, "WIN") !== false)
        {
            //win下會科學計數 格式化
            return number_format(bindec($base), 0, '', ''); //輸出16位id 4369796497932289
        }
        return bindec($base); // 輸出19位id 4940315248386113537 
    }
    //機器id 10位元組 1-1023
    public static function get_mid()
{
        if (self::$SEQUENCE_NUM >= 4095)
        {
            self::$LAST_TIME_MID++;
            if (self::$LAST_TIME_MID > 1023)
            {
                self::$LAST_TIME_MID = 0;
            }
        }
    }
    //12位元組 1-4095
    public static function get_num()
{
        self::$SEQUENCE_NUM++;
        if (self::$SEQUENCE_NUM > 4095)
        {
            self::$SEQUENCE_NUM = 0;
        }
    }
}
for($i=1;$i<=100000;$i++)
{
var_dump(Snowflake::createOnlyId());
// string(19) "5018983989168308224"
}