概述
分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。 该项目地址为:https://github.com/twitter/snowflake是用Scala实现的。
雪花算法: (提高聚集索引的性能)
雪花ID是用一个64位的整形数字来做ID,对应.net中的long,数据库中的bigint,雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号等。
自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景。
GUID:采用无意义字符串,数据量增大时造成访问过慢,且不宜排序。
算法描述:
- 最高位是符号位,始终为0,不可用。
- 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
- 10位的机器标识,10位的长度最多支持部署1024个节点。
- 12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
结构
snowflake的结构如下(每部分用-分开):
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
一共加起来刚好64位,为一个Long型。(转换成字符串长度为18)
snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。据说:snowflake每秒能够产生26万个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 }
调用方法:
1 IdWorker idworker = new IdWorker(1); 2 for (int i = 0; i < 1000; i++) 3 { 4 Console.WriteLine(idworker.nextId()); 5 }