經常使用的分佈式ID生成策略

分佈式的惟一ID是一個系統設計過程當中常常遇到的問題。生成分佈式ID的策略有多種,應用場景也不盡相同。下面簡單介紹一下一些經常使用的分佈式ID生成策略:java

  1. 數據庫自增ID
  2. UUID
  3. Redis
  4. Snowflake

(一)數據庫自增ID

經過定義數據庫的自增ID進行實現。git

優勢:github

  1. 簡單,使用數據庫現成的實現。
  2. ID惟一,不會重複。
  3. ID遞增,有序增加,可設置步長。

缺點:redis

  1. 存在單點寫入問題。一般在數據庫主-從架構的狀況下,若是主庫掛了,切換到從庫的話,頗有可能生成重複的ID,致使數據錯亂。
  2. 擴展性差並存在自增上限。

優化:算法

  1. 在寫多庫的狀況下,可經過設置不一樣步長避免衝突。如A庫(1, 3, 5, 7) 、B庫(2, 4, 6, 8)
  2. 能夠經過集中式的ID生成。如利用一張表記錄當前ID的最大值(如:10),每次集中取一批ID(如:11,12,13,14,15,16),當這批ID用完後,更新最大ID爲16,再取下一批。

(二)UUID

經過本地代碼或者數據庫生成。數據庫

優勢:架構

  1. 全球惟一,擴展性強,理論上不會出現重複。
  2. 性能好,本地能夠生成,不須要遠程交互。

缺點:less

  1. 一般是字符串存儲,ID較長,做爲主鍵創建索引查詢時性能差。
  2. ID隨機生成,沒有連續性。

優化:dom

  1. 將uuid轉成int64。這樣既減小空間也能優化查詢效率。
private static BigInteger getBigIntegerFromUuid() {
        UUID id = UUID.randomUUID();
        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
        bb.putLong(id.getMostSignificantBits());
        bb.putLong(id.getLeastSignificantBits());
        return new BigInteger(1, bb.array());
    }

(三)Redis

經過redis的incrby或者lua方式進行生成ID。分佈式

(四)Snowflake

Snowflake算法源自Twitter,主要應用於解決分佈式的場景下的惟一ID。

優勢:

  1. 本地生成, 擴展性好,性能優異。
  2. 生成的ID簡單、有序。

缺點:

  1. 使用時間進行計算,不一樣機器的時鐘可能會形成生成的ID並不必定嚴格有序。

改進:

  1. 能夠根據實際的需求對算法進行調整。

下面簡單介紹一下Snowflake的基本原理。

snowflake使用64bit的存儲空間,正好是一個長整數的存儲空間。

時間前綴(42) + 機房標識(5) + 主機標識(5) + 順序號(12)

時間前綴:

使用秒數記錄。須要設置一個起始時間點(如:2016-01-01 00:00:00)。那麼生成ID時,將當前的秒數-起始時間的秒數。年T = (1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139.46,表示這個算法能夠支持使用139.46年。

機房標識:

能夠用於表示機房編號之類。按(1L << 5) = 32計算,能夠支持32個機房。

主機標識:

能夠用於表示主機編號之類。按(1L << 5) = 32計算,能夠支持32個主機。綜合機房標識和主機標識,一共能夠支持使用1024臺主機。

順序號:

按(1L << 12) = 4096計算,每秒能夠生成4096個ID。再綜合機房、主機、順序號,1024*4096約400萬,即每秒能夠生成400萬個不重複的ID。

參考:https://github.com/twitter/snowflake

注意:能夠根據實際狀況對snowflake的不一樣組成位數進行調整。

  1. 如實際上機器只須要10臺的話,那機房和主機標識加起來4位便可。
  2. 如當機器少,可是要支持每秒生成的ID更多時,也能夠調整順序號的位數。

參考中的代碼使用scala編寫,下面是java版本的翻譯:

package com.zheng.coderepo.snowflake;

/**
 * Created by zhangchaozheng on 17-2-24.
 */
public class IdGen {
    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 IdGen(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();
    }
}
相關文章
相關標籤/搜索