天天看點

全局唯一ID生成器(Snowflake ID組成) 分析

Snowflake ID組成

Snowflake ID有64bits長,由以下三部分組成:

  • time—42bits,精确到ms,那就意味着其可以表示長達(2^42-1)/(1000360024*365)=139.5年,另外使用者可以自己定義一個開始紀元(epoch),然後用(目前時間-開始紀元)算出time,這表示在time這個部分在140年的時間裡是不會重複的,官方文檔在這裡寫成了41bits,應該是寫錯了。另外,這裡用time還有一個很重要的原因,就是可以直接更具time進行排序,對于twitter這種更新頻繁的應用,時間排序就顯得尤為重要了。
  • machine id—10bits,該部分其實由datacenterId和workerId兩部分組成,這兩部分是在配置檔案中指明的。
  • datacenterId的作用(個人看法)

    1.友善搭建多個生成uid的service,并保證uid不重複,比如在datacenter0将機器0,1,2組成了一個生成uid的service,而datacenter1此時也需要一個生成uid的service,從本中心擷取uid顯然是最快最友善的,那麼它可以在自己中心搭建,隻要保證datacenterId唯一。如果沒有datacenterId,即用10bits,那麼在搭建一個新的service前必須知道目前已經在用的id,否則不能保證生成的id唯一,比如搭建的兩個uid service中都有machine id為100的機器,如果其server時間相同,那麼産生相同id的情況不可避免。

    2.加快server啟動速度。啟動一台uid server時,會去檢查zk同workerId目錄中其他機器的情況,如其在zk上注冊的id和向它請求傳回的work_id是否相同,是否處同一個datacenter下,另外還會檢查該server的時間與目前已有機器的平均時間誤差是否在10s範圍内等,這些檢查是會耗費一定時間的。将一個datacenter下的機器數限制在32台(5bits)以内,在一定程度上也保證了server的啟動速度。

  • workerId是實際server機器的代号,最大到32,同一個datacenter下的workerId是不能重複的。它會被注冊到zookeeper上,確定workerId未被其他機器占用,并将host:port值存入,注冊成功後就可以對外提供服務了。
  • sequence id —12bits,該id可以表示4096個數字,它是在time相同的情況下,遞增該值直到為0,即一個循環結束,此時便隻能等到下一個ms到來,一般情況下4096/ms的請求是不太可能出現的,是以足夠使用了。

Snowflake ID便是通過這三部分實作了UID的産生,政策也并不複雜。下面我們來看看它的一些關鍵源碼

/**

* 核心代碼就是毫秒級時間41位+機器ID 10位+毫秒内序列12位

* */

public class IdWorker {

private long workerId; 

private long datacenterId; 

private long sequence = 0L; 

private long twepoch = 1288834974657L; 

private long workerIdBits = 5L; 

private long datacenterIdBits = 5L; 

private long maxWorkerId = -1L ^ (-1L << workerIdBits); 

private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 

private long sequenceBits = 12L; 

private long workerIdShift = sequenceBits; 

private long datacenterIdShift = sequenceBits + workerIdBits; 

private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; 

private long sequenceMask = -1L ^ (-1L << sequenceBits); 

private long lastTimestamp = -1L; 

public IdWorker(long workerId, long datacenterId) { 

// sanity check for workerId 

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) { 

System.out.println("clock is moving backwards. Rejecting requests until "+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) { 

while (timestamp <= lastTimestamp) { 

timestamp = timeGen(); 

return timestamp; 

protected long timeGen() { 

return System.currentTimeMillis(); 

}

繼續閱讀