先抄個雪花ID介紹,雪花演算法: 雪花演算法的原始版本是scala版,用於生成分散式ID(純數字,時間順序),訂單編號等。 自增ID:對於數據敏感場景不宜使用,且不適合於分散式場景。GUID:採用無意義字元串,數據量增大時造成訪問過慢,且不宜排序。 演算法描述: 最高位是符號位,始終為0,不可用。 41位 ...
先抄個雪花ID介紹,雪花演算法:
雪花ID是用一個64位的整形數字來做ID,對應.net中的long,資料庫中的bigint,雪花演算法的原始版本是scala版,用於生成分散式ID(純數字,時間順序),訂單編號等。
自增ID:對於數據敏感場景不宜使用,且不適合於分散式場景。
GUID:採用無意義字元串,數據量增大時造成訪問過慢,且不宜排序。
演算法描述:
- 最高位是符號位,始終為0,不可用。
- 41位的時間序列,精確到毫秒級,41位的長度可以使用69年。時間位還有一個很重要的作用是可以根據時間進行排序。
- 10位的機器標識,10位的長度最多支持部署1024個節點。
- 12位的計數序列號,序列號即一系列的自增id,可以支持同一節點同一毫秒生成多個ID序號,12位的計數序列號支持每個節點每毫秒產生4096個ID序號。
好了,回歸本人自己介紹:時鐘回撥
雪花ID嚴重依賴系統當前時間,當系統時間被人為反後調整時,演算法會出問題,可能會出重覆ID.Snowflake原演算法是在檢測到系統時間被回調後直接拋異常.本代碼在時鐘回撥後,會將生成的ID時間戳停留在最後一次時間戳上(當序列溢出時會往前走一毫秒),等待系統時間追上後即可以避過時鐘回撥問題.
這種處理方式的優點是時鐘回撥後不會異常,能一直生成出雪花ID,但缺點是雪花ID中的時間戳不是系統的當前時間,會是回撥前的最後記錄的一次時間戳,但相差也不大.不知道有沒有什麼生產系統會對這個時間戳要求非常嚴格,無法使用這種補救方式的?
當然停掉系統後的時鐘回撥是無法處理的,這種還是會有可能出現重覆ID的.
介紹完畢,下麵直接上源碼吧,,本源碼除了生成雪花ID外,還提供解析雪花ID的方法.
源碼git地址: https://gitee.com/itsm/learning_example/tree/master/snowflake-雪花Id
1 public class SnowflakeId 2 { 3 4 // 開始時間截((new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)-Jan1st1970).TotalMilliseconds) 5 private const long twepoch = 1577836800000L; 6 7 // 機器id所占的位數 8 private const int workerIdBits = 5; 9 10 // 數據標識id所占的位數 11 private const int datacenterIdBits = 5; 12 13 // 支持的最大機器id,結果是31 (這個移位演算法可以很快的計算出幾位二進位數所能表示的最大十進位數) 14 private const long maxWorkerId = -1L ^ (-1L << workerIdBits); 15 16 // 支持的最大數據標識id,結果是31 17 private const long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 18 19 // 序列在id中占的位數 20 private const int sequenceBits = 12; 21 22 // 數據標識id向左移17位(12+5) 23 private const int datacenterIdShift = sequenceBits + workerIdBits; 24 25 // 機器ID向左移12位 26 private const int workerIdShift = sequenceBits; 27 28 29 // 時間截向左移22位(5+5+12) 30 private const int timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; 31 32 // 生成序列的掩碼,這裡為4095 (0b111111111111=0xfff=4095) 33 private const long sequenceMask = -1L ^ (-1L << sequenceBits); 34 35 // 數據中心ID(0~31) 36 public long datacenterId { get; private set; } 37 38 // 工作機器ID(0~31) 39 public long workerId { get; private set; } 40 41 // 毫秒內序列(0~4095) 42 public long sequence { get; private set; } 43 44 // 上次生成ID的時間截 45 public long lastTimestamp { get; private set; } 46 47 48 /// <summary> 49 /// 雪花ID 50 /// </summary> 51 /// <param name="datacenterId">數據中心ID</param> 52 /// <param name="workerId">工作機器ID</param> 53 public SnowflakeId(long datacenterId,long workerId ) 54 { 55 if (datacenterId > maxDatacenterId || datacenterId < 0) 56 { 57 throw new Exception(string.Format("datacenter Id can't be greater than {0} or less than 0", maxDatacenterId)); 58 } 59 if (workerId > maxWorkerId || workerId < 0) 60 { 61 throw new Exception(string.Format("worker Id can't be greater than {0} or less than 0", maxWorkerId)); 62 } 63 this.workerId = workerId; 64 this.datacenterId = datacenterId; 65 this.sequence = 0L; 66 this.lastTimestamp = -1L; 67 } 68 69 /// <summary> 70 /// 獲得下一個ID 71 /// </summary> 72 /// <returns></returns> 73 public long NextId() 74 { 75 lock (this) 76 { 77 long timestamp = GetCurrentTimestamp(); 78 if (timestamp > lastTimestamp) //時間戳改變,毫秒內序列重置 79 { 80 sequence = 0L; 81 } 82 else if (timestamp == lastTimestamp) //如果是同一時間生成的,則進行毫秒內序列 83 { 84 sequence = (sequence + 1) & sequenceMask; 85 if (sequence == 0) //毫秒內序列溢出 86 { 87 timestamp = GetNextTimestamp(lastTimestamp); //阻塞到下一個毫秒,獲得新的時間戳 88 } 89 } 90 else //當前時間小於上一次ID生成的時間戳,證明系統時鐘被回撥,此時需要做回撥處理 91 { 92 sequence = (sequence + 1) & sequenceMask; 93 if (sequence > 0) 94 { 95 timestamp = lastTimestamp; //停留在最後一次時間戳上,等待系統時間追上後即完全度過了時鐘回撥問題。 96 } 97 else //毫秒內序列溢出 98 { 99 timestamp = lastTimestamp + 1; //直接進位到下一個毫秒 100 } 101 //throw new Exception(string.Format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp)); 102 } 103 104 lastTimestamp = timestamp; //上次生成ID的時間截 105 106 //移位並通過或運算拼到一起組成64位的ID 107 var id= ((timestamp - twepoch) << timestampLeftShift) 108 | (datacenterId << datacenterIdShift) 109 | (workerId << workerIdShift) 110 | sequence; 111 return id; 112 } 113 } 114 115 /// <summary> 116 /// 解析雪花ID 117 /// </summary> 118 /// <returns></returns> 119 public static string AnalyzeId(long Id) 120 { 121 StringBuilder sb = new StringBuilder(); 122 123 var timestamp = (Id >> timestampLeftShift) ; 124 var time = Jan1st1970.AddMilliseconds(timestamp + twepoch); 125 sb.Append(time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss:fff")); 126 127 var datacenterId = (Id ^ (timestamp << timestampLeftShift)) >> datacenterIdShift; 128 sb.Append("_" + datacenterId); 129 130 var workerId = (Id ^ ((timestamp << timestampLeftShift) | (datacenterId << datacenterIdShift))) >> workerIdShift; 131 sb.Append("_" + workerId); 132 133 var sequence = Id & sequenceMask; 134 sb.Append("_" + sequence); 135 136 return sb.ToString(); 137 } 138 139 /// <summary> 140 /// 阻塞到下一個毫秒,直到獲得新的時間戳 141 /// </summary> 142 /// <param name="lastTimestamp">上次生成ID的時間截</param> 143 /// <returns>當前時間戳</returns> 144 private static long GetNextTimestamp(long lastTimestamp) 145 { 146 long timestamp = GetCurrentTimestamp(); 147 while (timestamp <= lastTimestamp) 148 { 149 timestamp = GetCurrentTimestamp(); 150 } 151 return timestamp; 152 } 153 154 /// <summary> 155 /// 獲取當前時間戳 156 /// </summary> 157 /// <returns></returns> 158 private static long GetCurrentTimestamp() 159 { 160 return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds; 161 } 162 163 private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 164 }