分布式唯一ID:雪花ID Snowflake .Net版

先抄个雪花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     }

 

posted @ 2020-01-07 14:37  sunyuliang  阅读(23389)  评论(40编辑  收藏  举报