Twitter-Snowflake,64位自增ID算法詳解

1、背景

Twitter-Snowflake算法產(chǎn)生的背景相當(dāng)簡單,為了滿足Twitter每秒上萬條消息的請求,每條消息都必須分配一條唯一的id,這些id還需要一些大致的順序(方便客戶端排序),并且在分布式系統(tǒng)中不同機器產(chǎn)生的id必須不同。

2、Snowflake算法核心

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

snowflake

除了最高位bit標(biāo)記為不可用以外,其余三組bit占位均可浮動,看具體的業(yè)務(wù)需求而定。默認情況下41bit的時間戳可以支持該算法使用到2082年,10bit的工作機器id可以支持1023臺機器,序列號支持1毫秒產(chǎn)生4095個自增序列id。下文會具體分析。

2.1 Snowflake – 時間戳

這里時間戳的細度是毫秒級,具體代碼如下,建議使用64位linux系統(tǒng)機器,因為有vdso,gettimeofday()在用戶態(tài)就可以完成操作,減少了進入內(nèi)核態(tài)的損耗。

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使用,那么根據(jù)同樣的算法最后年份 = 17.4年。

2. 2 Snowflake – 工作機器id

嚴(yán)格意義上來說這個bit段的使用可以是進程級,機器級的話你可以使用MAC地址來唯一標(biāo)示工作機器,工作進程級可以使用IP+Path來區(qū)分工作進程。如果工作機器比較少,可以使用配置文件來設(shè)置這個id是一個不錯的選擇,如果機器過多配置文件的維護是一個災(zāi)難性的事情。

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


工作進程與工作id分配器只是在工作進程啟動的時候交互一次,然后工作進程可以自行將分配的id數(shù)據(jù)落文件,下一次啟動直接讀取文件里的id使用。

PS:這個工作機器id的bit段也可以進一步拆分,比如用前5個bit標(biāo)記進程id,后5個bit標(biāo)記線程id之類:D

2.3 Snowflake – 序列號

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

uint64_t waitNextMs(uint64_t lastStamp)
{
    uint64_t cur = 0;
    do {
        cur = generateStamp();
    } while (cur <= lastStamp);
    return cur;
}

總體來說,是一個很高效很方便的GUID產(chǎn)生算法,一個int64_t字段就可以勝任,不像現(xiàn)在主流128bit的GUID算法,即使無法保證嚴(yán)格的id序列性,但是對于特定的業(yè)務(wù),比如用做游戲服務(wù)器端的GUID產(chǎn)生會很方便。另外,在多線程的環(huán)境下,序列號使用atomic可以在代碼實現(xiàn)上有效減少鎖的密度。

3、Snowflake - 算法實現(xiàn)(Java)

public class IdWorker {
    private final long twepoch = 1288834974657L;
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long sequenceBits = 12L;
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public IdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();
        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;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    protected long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        IdWorker idWorker = new IdWorker(0, 0);
        for (int i = 0; i < 100; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容