背景
當下絕大部分互聯網公司采用的是分布式的架構系統,而分布式系統中有一些場景需要使用到全局性唯一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位序列號。
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 