分佈式雪花算法獲取id

實現全局惟一ID

1、採用主鍵自增

最多見的方式。利用數據庫,全數據庫惟一。java

優勢:mysql

  1)簡單,代碼方便,性能能夠接受。git

  2)數字ID自然排序,對分頁或者須要排序的結果頗有幫助。github

缺點:算法

  1)不一樣數據庫語法和實現不一樣,數據庫遷移的時候或多數據庫版本支持的時候須要處理。sql

  2)在單個數據庫或讀寫分離或一主多從的狀況下,只有一個主庫能夠生成。有單點故障的風險。數據庫

  3)在性能達不到要求的狀況下,比較難於擴展安全

  4)若是碰見多個系統須要合併或者涉及到數據遷移會至關痛苦。服務器

  5)分表分庫的時候會有麻煩less

2、UUID

常見的方式。能夠利用數據庫也能夠利用程序生成,通常來講全球惟一。

優勢:

  1)簡單,代碼方便。

  2)生成ID性能很是好,基本不會有性能問題。

  3)全球惟一,在碰見數據遷移,系統數據合併,或者數據庫變動等狀況下,能夠從容應對。

缺點:

  1)沒有排序,沒法保證趨勢遞增

  2)UUID每每是使用字符串存儲,查詢的效率比較低

  3)存儲空間比較大,若是是海量數據庫,就須要考慮存儲量的問題。

  4)傳輸數據量大

  5)插入數據慢,由於mysql採用的B+tree的結構來存儲索引,假如是數據庫自帶的那種主鍵自增,節點滿了,會裂變出新的節點,新節點滿了,再去裂變新的節點,這樣利用率和效率都很高。而UUID是無序的,會形成中間節點的分裂,也會形成不飽和的節點,插入的效率天然就比較低下了。

3、Redis生成ID

  當使用數據庫來生成ID性能不夠要求的時候,咱們能夠嘗試使用Redis來生成ID。這主要依賴於Redis是單線程的,因此也能夠用生成全局惟一的ID。能夠用Redis的原子操做 INCR和INCRBY來實現。可使用Redis集羣來獲取更高的吞吐量。假如一個集羣中有5臺Redis。能夠初始化每臺Redis的值分別是1,2,3,4,5,而後步長都是5。各個Redis生成的ID爲:

A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25

  這個,隨便負載到哪一個機肯定好,將來很難作修改。可是3-5臺服務器基本可以知足器上,均可以得到不一樣的ID。可是步長和初始值必定須要事先須要了。使用Redis集羣也能夠方式單點故障的問題。另外,比較適合使用Redis來生成天天從0開始的流水號。好比訂單號=日期+當日自增加號。能夠天天在Redis中生成一個Key,使用INCR進行累加。

優勢:

  1)不依賴於數據庫,靈活方便,且性能優於數據庫。

  2)數字ID自然排序,對分頁或者須要排序的結果頗有幫助。

缺點:

  1)若是系統中沒有Redis,還須要引入新的組件,增長系統複雜度

  2)須要編碼和配置的工做量比較大

4、雪花算法 (snowflake,Java版)

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

     

  • 1位,不用。二進制中最高位爲1的都是負數,可是咱們生成的id通常都使用整數,因此這個最高位固定是0
  • 41位,用來記錄時間戳(毫秒)。

    • 41位能夠表示$2^{41}-1$個數字,
    • 若是隻用來表示正整數(計算機中正數包含0),能夠表示的數值範圍是:0 至 $2^{41}-1$,減1是由於可表示的數值範圍是從0開始算的,而不是1。
    • 也就是說41位能夠表示$2^{41}-1$個毫秒的值,轉化成單位年則是$(2^{41}-1) / (1000 * 60 * 60 * 24 * 365) = 69$年
  • 10位,用來記錄工做機器id。

    • 能夠部署在$2^{10} = 1024$個節點,包括5位datacenterId5位workerId
    • 5位(bit)能夠表示的最大正整數是$2^{5}-1 = 31$,便可以用0、一、二、三、....31這32個數字,來表示不一樣的datecenterId或workerId
  • 12位,序列號,用來記錄同毫秒內產生的不一樣id。

    • 12位(bit)能夠表示的最大正整數是$2^{12}-1 = 4095$,便可以用0、一、二、三、....4094這4095個數字,來表示同一機器同一時間截(毫秒)內產生的4095個ID序號

  因爲在Java中64bit的整數是long類型,因此在Java中SnowFlake算法生成的id就是long(18位)來存儲的。

優勢

  1)全部生成的id按時間趨勢遞增

  2)整個分佈式系統內不會產生重複id(由於有datacenterId和workerId來作區分)

  3)不依賴於其餘系統,可直接編寫

缺點:

  1)時鐘回撥:最多見的問題就是時鐘回撥致使的ID重複問題,在SnowFlake算法中並無什麼有效的解法,僅是拋出異常。時鐘回撥涉及兩種狀況①實例停機→時鐘回撥→實例重啓→計算ID ②實例運行中→時鐘回撥→計算ID

  2)手動配置:另外一個就是workerId(機器ID)是須要部署時手動配置,而workerId又不能重複。幾臺實例還好,一旦實例達到必定量級,管理workerId將是一個複雜的操做。

美團的Leaf和百度的UidGenerator有相應的解決方案

  如下是Twitter官方原版的,用Scala寫的:

/** Copyright 2010-2012 Twitter, Inc.*/
package com.twitter.service.snowflake

import com.twitter.ostrich.stats.Stats
import com.twitter.service.snowflake.gen._
import java.util.Random
import com.twitter.logging.Logger

/**
 * An object that generates IDs.
 * This is broken into a separate class in case
 * we ever want to support multiple worker threads
 * per process
 */
class IdWorker(val workerId: Long, val datacenterId: Long, private val reporter: Reporter, var sequence: Long = 0L)
extends Snowflake.Iface {
  private[this] def genCounter(agent: String) = {
    Stats.incr("ids_generated")
    Stats.incr("ids_generated_%s".format(agent))
  }
  private[this] val exceptionCounter = Stats.getCounter("exceptions")
  private[this] val log = Logger.get
  private[this] val rand = new Random

  val twepoch = 1288834974657L

  private[this] val workerIdBits = 5L
  private[this] val datacenterIdBits = 5L
  private[this] val maxWorkerId = -1L ^ (-1L << workerIdBits)
  private[this] val maxDatacenterId = -1L ^ (-1L << datacenterIdBits)
  private[this] val sequenceBits = 12L

  private[this] val workerIdShift = sequenceBits
  private[this] val datacenterIdShift = sequenceBits + workerIdBits
  private[this] val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits
  private[this] val sequenceMask = -1L ^ (-1L << sequenceBits)

  private[this] var lastTimestamp = -1L

  // sanity check for workerId
  if (workerId > maxWorkerId || workerId < 0) {
    exceptionCounter.incr(1)
    throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0".format(maxWorkerId))
  }

  if (datacenterId > maxDatacenterId || datacenterId < 0) {
    exceptionCounter.incr(1)
    throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0".format(maxDatacenterId))
  }

  log.info("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
    timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId)

  def get_id(useragent: String): Long = {
    if (!validUseragent(useragent)) {
      exceptionCounter.incr(1)
      throw new InvalidUserAgentError
    }

    val id = nextId()
    genCounter(useragent)

    reporter.report(new AuditLogEntry(id, useragent, rand.nextLong))
    id
  }

  def get_worker_id(): Long = workerId
  def get_datacenter_id(): Long = datacenterId
  def get_timestamp() = System.currentTimeMillis

  protected[snowflake] def nextId(): Long = synchronized {
    var timestamp = timeGen()

    if (timestamp < lastTimestamp) {
      exceptionCounter.incr(1)
      log.error("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
      throw new InvalidSystemClock("Clock moved backwards.  Refusing to generate id for %d milliseconds".format(
        lastTimestamp - timestamp))
    }

    if (lastTimestamp == timestamp) {
      sequence = (sequence + 1) & sequenceMask
      if (sequence == 0) {
        timestamp = tilNextMillis(lastTimestamp)
      }
    } else {
      sequence = 0
    }

    lastTimestamp = timestamp
    ((timestamp - twepoch) << timestampLeftShift) |
      (datacenterId << datacenterIdShift) |
      (workerId << workerIdShift) |
      sequence
  }

  protected def tilNextMillis(lastTimestamp: Long): Long = {
    var timestamp = timeGen()
    while (timestamp <= lastTimestamp) {
      timestamp = timeGen()
    }
    timestamp
  }

  protected def timeGen(): Long = System.currentTimeMillis()

  val AgentParser = """([a-zA-Z][a-zA-Z\-0-9]*)""".r

  def validUseragent(useragent: String): Boolean = useragent match {
    case AgentParser(_) => true
    case _ => false
  }
}
View Code

  使用java:

public class SnowflakeIdWorker {
    /**
     * 開始時間截 (2015-01-01)
     */
    private final long twepoch = 1420041600000L;
    /**
     * 機器id所佔的位數
     */
    private final long workerIdBits = 5L;
    /**
     * 數據標識id所佔的位數
     */
    private final long datacenterIdBits = 5L;
    /**
     * 支持的最大機器id,結果是31 (這個移位算法能夠很快的計算出幾位二進制數所能表示的最大十進制數)
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    /**
     * 支持的最大數據標識id,結果是31
     */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    /**
     * 序列在id中佔的位數
     */
    private final long sequenceBits = 12L;
    /**
     * 機器ID向左移12位
     */
    private final long workerIdShift = sequenceBits;
    /**
     * 數據標識id向左移17位(12+5)
     */
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    /**
     * 時間截向左移22位(5+5+12)
     */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    /**
     * 生成序列的掩碼,這裏爲4095 (0b111111111111=0xfff=4095)
     */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    /**
     * 工做機器ID(0~31)
     */
    private long workerId;
    /**
     * 數據中心ID(0~31)
     */
    private long datacenterId;
    /**
     * 毫秒內序列(0~4095)
     */
    private long sequence = 0L;
    /**
     * 上次生成ID的時間截
     */
    private long lastTimestamp = -1L;
    /**
     * 構造函數
     * @param workerId     工做ID (0~31)
     * @param datacenterId 數據中心ID (0~31)
     */
    public SnowflakeIdWorker(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;
    }
    /**
     * 得到下一個ID (該方法是線程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        // 若是當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過這個時候應當拋出異常
        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;
        }
        // 上次生成ID的時間截
        lastTimestamp = timestamp;
        // 移位並經過或運算拼到一塊兒組成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }
    /**
     * 阻塞到下一個毫秒,直到得到新的時間戳
     * @param lastTimestamp 上次生成ID的時間截
     * @return 當前時間戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    /**
     * 返回以毫秒爲單位的當前時間
     * @return 當前時間(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) throws InterruptedException {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
        for (int i = 0; i < 10; i++) {
            long id = idWorker.nextId();
            Thread.sleep(1);
            System.out.println(id);
        }
    }
}
相關文章
相關標籤/搜索