分布式ID生成器-雪花算法(snowflake)


背景

當下絕大部分互聯網公司采用的是分布式的架構系統,而分布式系統中有一些場景需要使用到全局性唯一ID,例如:訂單編號、付款單編號、交易流水號等等,在這之前,我們可以使用UUID、數據庫自增ID等去實現它,但是要么生成的ID是無序的,要么ID生成效率低下。
所以在該背景下,twitter公司提出了snowflake算法,最初Twitter把存儲系統從MySQL遷移到Cassandra,因為Cassandra沒有順序ID生成機制,為了滿足Twitter每秒上萬條消息的請求,每條消息都必須分配一條唯一的id,這些id還需要一些大致的順序(方便客戶端排序),並且在分布式系統中不同機器產生的id必須不同,所以twitter開發了這樣一套全局唯一ID生成服務。

介紹
snowflake算法生成的64位ID = 1位符號位 + 41位時間戳 + 10位機器id + 12位序列號。


 
64位ID組成結構.png

1、符號位:一般默認為0,表明該ID為正數。

2、時間戳:該時間戳並不是指當前的時間戳,而是當前時間戳-初始時間戳(初始時間戳離當前時間越近越好,而且必須小於當前時間戳)的值。這樣做的好處是有更多的數值可以使用,最多可以使用(1L << 41)/ (1000毫秒 * 60秒 * 60 分鍾 * 24小時 * 365天) = 69年。

3、機器id:取值在0 ~ 1023之間,最多支持1024個節點。
假設你們公司的機器節點超過了1024個,而你們的業務沒有達到1毫秒需要2408個序列號的並發的情況下,可以將序列號的位數減少,給機器id增加位數。

4、序列號,取值在0~4096之間,最多支持一毫秒內生成4096個序列號。
假設你們公司的機器節點沒有超過512個,而你們的業務1毫秒需要超過4096個序列號的並發的情況下,可以將機器id的位數減少,給序列號增加位數。

優點:
1、完全基於內存,ID生成效率高。
2、生成的ID基於時間戳和序列號,具有有序性的特點,方便排序、查詢。
3、可以根據生產機器節點數和業務並發量的情況,調整ID的生成策略。

代碼:

package com.jiepos.api.demo; /** * 雪花算法 * 64位ID = 1位符號位(固定為0,表示正數) + 41位時間戳 + 10位工作機器id + 12位序列號 * @author shuyan.qi * @date 2020/5/3 9:42 下午 */ public class SnowFlakeID { //12位序列號 private long sequence;//序列號 private long sequenceBits = 12L;//序列號位數 private long maxSequence = -1 ^ (-1 << sequenceBits);//序列號最大值 //10位工作機器id = 5位機房id + 5位機器id private long workerId;//機器id private long workerIdBits = 5L;//機器id位數 private long maxWorkerId = -1 ^ (-1 << workerIdBits);//機器id最大值 private long workerIdMoveBits = sequenceBits;//機器id左移位數 = 序列號位數 private long workerIdAfterMove = 0L;//左移后的機器id private long workerCenterId;//機房id private long workerCenterIdBits = 5L;//機房id位數 private long maxWorkerCenterId = -1 ^ (-1 << workerCenterIdBits);//機房id最大值 private long workerCenterIdMoveBits = workerIdBits + workerIdMoveBits;//機房id左移位數 = 機器id位數 + 序列號位數 private long workerCenterIdAfterMove = 0L;//左移后的機房id //41位時間戳 private long lastTimestamp = -1L;//默認-1L private long initTimestamp = 1588518046057L;//初始時間戳 private long timestampMoveBits = workerCenterIdBits + workerCenterIdMoveBits;//時間戳左移位數 = 機房id位數 + 機器id位數 + 序列號位數 public SnowFlakeID(long workerCenterId , long workerId){ if(workerCenterId < 0 || workerCenterId > maxWorkerCenterId){ throw new IllegalArgumentException("workerCenterId is illegal"); } if(workerId < 0 || workerId > maxWorkerId){ throw new IllegalArgumentException("workerId is illegal"); } this.workerCenterId = workerCenterId; this.workerId = workerId; this.workerCenterIdAfterMove = this.workerCenterId << this.workerCenterIdMoveBits; this.workerIdAfterMove = this.workerCenterId << this.workerCenterIdMoveBits; } /** * 生成ID的核心方法 */ public synchronized long nextId(){ long currentTimestamp = timestamp(); if(currentTimestamp < lastTimestamp){ String s = String.format("currentTimestamp is earlier than lastTimestamp,lastTimestamp=%s,currentTimestamp=%s",lastTimestamp,currentTimestamp); System.out.println(s); //throw new RuntimeException(s); // 時鍾回撥后手動撥正。 // 因為依賴lastTimestamp,所以重啟后第一次就發生時鍾回撥的情況無法處理。 // 可以將lastTimestamp存放到redis之類第三方緩存中,但這樣生成id的效率會降低,請開發者根據實際情況去選擇。 currentTimestamp = lastTimestamp; } if(currentTimestamp == lastTimestamp){ //同一時間戳,序列號加1 sequence = (sequence + 1) & maxSequence; if(sequence == 0L){ //如果序列號加1后的值為0,表示當前時間戳內的序列號已用完,需要獲取下一個時間戳 currentTimestamp = nextTimestamp(currentTimestamp); } }else{ sequence = 0L;//不同時間戳,重置序列號 } lastTimestamp = currentTimestamp;//更新成功生成id的最新時間戳 return ((currentTimestamp - initTimestamp) << timestampMoveBits) | workerCenterIdAfterMove | workerIdAfterMove | sequence; } /** * 獲取timestamp的下一毫秒數 * @param timestamp 當前毫秒數 * @return */ public long nextTimestamp(long timestamp){ long timestamp1 = 0L; do{ timestamp1 = timestamp(); }while (timestamp >= timestamp1); return timestamp1; } /** * 獲取當前時間戳 * @return */ public long timestamp(){ return System.currentTimeMillis(); } public static void main(String[] args) throws InterruptedException { /* //測試並發 Map<String,Object> map = new ConcurrentHashMap<>(); SnowFlakeID snowFlakeID = new SnowFlakeID(1, 1); for(int i = 0;i < 100;i++){ new Thread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } for (int j = 0 ;j < 100000;j++){ map.put(String.valueOf(snowFlakeID.nextId()),1); } }).start();; } for(;;){ TimeUnit.SECONDS.sleep(1); System.out.println("size="+map.size()); } */ /* //測試速度 SnowFlakeID snowFlakeID = new SnowFlakeID(1, 1); long startTime = System.currentTimeMillis(); for(int i = 0;i < 3000000;i++){ snowFlakeID.nextId(); } System.out.println("耗時:"+(System.currentTimeMillis() - startTime)/1000.0d + "秒"); */ } 


作者:鋼鐵加魯魯_d59c
鏈接:https://www.jianshu.com/p/d230443d0e60
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM