概述
分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。
有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一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型。(转换成字符串后长度最多19)
snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。
源码
(JAVA版本的源码)
1 package com.xazhxc.htjcom.back.controller.base; 2 3 import lombok.extern.slf4j.Slf4j; 4 5 /** 6 * 原作者 zzxadi https://github.com/zzxadi/Snowflake-IdWorker 7 * @author Exrickx 8 */ 9 @Slf4j 10 public class SnowFlakeUtil { 11 12 private final long id; 13 /** 14 * 时间起始标记点,作为基准,一般取系统的最近时间 15 */ 16 private final long epoch = 1524291141010L; 17 /** 18 * 机器标识位数 19 */ 20 private final long workerIdBits = 10L; 21 /** 22 * 机器ID最大值: 1023 23 */ 24 private final long maxWorkerId = -1L ^ -1L << this.workerIdBits; 25 /** 26 * 0,并发控制 27 */ 28 private long sequence = 0L; 29 /** 30 * 毫秒内自增位 31 */ 32 private final long sequenceBits = 12L; 33 34 /** 35 * 12 36 */ 37 private final long workerIdShift = this.sequenceBits; 38 /** 39 * 22 40 */ 41 private final long timestampLeftShift = this.sequenceBits + this.workerIdBits; 42 /** 43 * 4095,111111111111,12位 44 */ 45 private final long sequenceMask = -1L ^ -1L << this.sequenceBits; 46 private long lastTimestamp = -1L; 47 48 private SnowFlakeUtil(long id) { 49 if (id > this.maxWorkerId || id < 0) { 50 throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", this.maxWorkerId)); 51 } 52 this.id = id; 53 } 54 55 public synchronized long nextId() { 56 long timestamp = timeGen(); 57 if (this.lastTimestamp == timestamp) { 58 //如果上一个timestamp与新产生的相等,则sequence加一(0-4095循环); 对新的timestamp,sequence从0开始 59 this.sequence = this.sequence + 1 & this.sequenceMask; 60 if (this.sequence == 0) { 61 // 重新生成timestamp 62 timestamp = this.tilNextMillis(this.lastTimestamp); 63 } 64 } else { 65 this.sequence = 0; 66 } 67 68 if (timestamp < this.lastTimestamp) { 69 log.error(String.format("clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp))); 70 return -1; 71 } 72 73 this.lastTimestamp = timestamp; 74 return timestamp - this.epoch << this.timestampLeftShift | this.id << this.workerIdShift | this.sequence; 75 } 76 77 private static SnowFlakeUtil flowIdWorker = new SnowFlakeUtil(1); 78 public static SnowFlakeUtil getFlowIdInstance() { 79 return flowIdWorker; 80 } 81 82 /** 83 * 等待下一个毫秒的到来, 保证返回的毫秒数在参数lastTimestamp之后 84 */ 85 private long tilNextMillis(long lastTimestamp) { 86 long timestamp = timeGen(); 87 while (timestamp <= lastTimestamp) { 88 timestamp = timeGen(); 89 } 90 return timestamp; 91 } 92 93 /** 94 * 获得系统当前毫秒数 95 */ 96 private static long timeGen() { 97 return System.currentTimeMillis(); 98 } 99 100 public static void main(String[] args) { 101 for(int i=0;i<100;i++){ 102 SnowFlakeUtil snowFlakeUtil = SnowFlakeUtil.getFlowIdInstance(); 103 System.out.println(snowFlakeUtil.nextId()); 104 } 105 } 106 }