先抄個雪花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位的ID107 var id= ((timestamp - twepoch) << timestampLeftShift)108 | (datacenterId << datacenterIdShift)109 | (workerId << workerIdShift)110 | sequence;111 return id;112 }113 }114 115 /// <summary>116 /// 決議雪花ID117 /// </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 }
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/75410.html
標籤:.NET Core
