一、全局ID的应用场景

在分布式系统生成一个全局唯一的ID

二、全局ID的实现方案 / 为什么使用SnowFlake?

· 一般情况,实现全局唯一ID,有三种方案,分别是通过中间件方式、UUID、雪花算法。

  方案一,通过中间件方式,可以是把数据库或者redis缓存作为媒介,从中间件获取ID。这种呢,优点是可以体现全局的递增趋势(优点只能想到这个),缺点呢,倒是一大堆,比如,依赖中间件,假如中间件挂了,就不能提供服务了;依赖中间件的写入和事务,会影响效率;数据量大了的话,你还得考虑部署集群,考虑走代理。这样的话,感觉问题复杂化了

  方案二,通过UUID的方式,java.util.UUID就提供了获取UUID的方法,使用UUID来实现全局唯一ID,优点是操作简单,也能实现全局唯一的效果,缺点呢,就是不能体现全局视野的递增趋势;太长了,UUID是32位,有点浪费;最重要的,是插入的效率低,因为呢,我们使用mysql的话,一般都是B+tree的结构来存储索引,假如是数据库自带的那种主键自增,节点满了,会裂变出新的节点,新节点满了,再去裂变新的节点,这样利用率和效率都很高。而UUID是无序的,会造成中间节点的分裂,也会造成不饱和的节点,插入的效率自然就比较低下了。

  方案三,Java写算法生成,可以直接使用本文提供的雪花算法也可以自己写算法

· 使用SnowFlake的原因:UUID太长了不好用

三、SnowFlakeID.java

  1 package com.miaoshaProject.demo;
  2 
  3 /**
  4  * @Author wangshuo
  5  * @Date 2022/5/8, 15:54
  6  * 基于雪花算法 (snowflake)生成全局ID
  7  */
  8 public class SnowFlakeID {
  9 
 10     // ==============================Fields===========================================
 11     /** 开始时间截 (2015-01-01) */
 12     private final long twepoch = 1420041600000L;
 13 
 14     /** 机器id所占的位数 */
 15     private final long workerIdBits = 5L;
 16 
 17     /** 数据标识id所占的位数 */
 18     private final long datacenterIdBits = 5L;
 19 
 20     /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
 21     private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 22 
 23     /** 支持的最大数据标识id,结果是31 */
 24     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 25 
 26     /** 序列在id中占的位数 */
 27     private final long sequenceBits = 12L;
 28 
 29     /** 机器ID向左移12位 */
 30     private final long workerIdShift = sequenceBits;
 31 
 32     /** 数据标识id向左移17位(12+5) */
 33     private final long datacenterIdShift = sequenceBits + workerIdBits;
 34 
 35     /** 时间截向左移22位(5+5+12) */
 36     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 37 
 38     /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
 39     private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 40 
 41     /** 工作机器ID(0~31) */
 42     private long workerId;
 43 
 44     /** 数据中心ID(0~31) */
 45     private long datacenterId;
 46 
 47     /** 毫秒内序列(0~4095) */
 48     private long sequence = 0L;
 49 
 50     /** 上次生成ID的时间截 */
 51     private long lastTimestamp = -1L;
 52 
 53     //==============================Constructors=====================================
 54     /**
 55      * 构造函数
 56      * @param workerId 工作ID (0~31)
 57      * @param datacenterId 数据中心ID (0~31)
 58      */
 59     public SnowFlakeID(long workerId, long datacenterId) {
 60         if (workerId > maxWorkerId || workerId < 0) {
 61             throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
 62         }
 63         if (datacenterId > maxDatacenterId || datacenterId < 0) {
 64             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
 65         }
 66         this.workerId = workerId;
 67         this.datacenterId = datacenterId;
 68     }
 69 
 70     // ==============================Methods==========================================
 71     /**
 72      * 获得下一个ID (该方法是线程安全的)
 73      * @return SnowflakeId
 74      */
 75     public synchronized long nextId() {
 76         long timestamp = timeGen();
 77 
 78         //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
 79         if (timestamp < lastTimestamp) {
 80             throw new RuntimeException(
 81                     String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
 82         }
 83 
 84         //如果是同一时间生成的,则进行毫秒内序列
 85         if (lastTimestamp == timestamp) {
 86             sequence = (sequence + 1) & sequenceMask;
 87             //毫秒内序列溢出
 88             if (sequence == 0) {
 89                 //阻塞到下一个毫秒,获得新的时间戳
 90                 timestamp = tilNextMillis(lastTimestamp);
 91             }
 92         }
 93         //时间戳改变,毫秒内序列重置
 94         else {
 95             sequence = 0L;
 96         }
 97 
 98         //上次生成ID的时间截
 99         lastTimestamp = timestamp;
100 
101         //移位并通过或运算拼到一起组成64位的ID
102         return ((timestamp - twepoch) << timestampLeftShift) //
103                 | (datacenterId << datacenterIdShift) //
104                 | (workerId << workerIdShift) //
105                 | sequence;
106     }
107 
108     /**
109      * 阻塞到下一个毫秒,直到获得新的时间戳
110      * @param lastTimestamp 上次生成ID的时间截
111      * @return 当前时间戳
112      */
113     protected long tilNextMillis(long lastTimestamp) {
114         long timestamp = timeGen();
115         while (timestamp <= lastTimestamp) {
116             timestamp = timeGen();
117         }
118         return timestamp;
119     }
120 
121     /**
122      * 返回以毫秒为单位的当前时间
123      * @return 当前时间(毫秒)
124      */
125     protected long timeGen() {
126         return System.currentTimeMillis();
127     }
128 
129     //==============================Test=============================================
130     /** 测试 */
131     public static void main(String[] args) {
132         SnowFlakeID idWorker = new SnowFlakeID(0, 0);
133 
134         for (int i = 0; i < 100; i++) {
135             long id = idWorker.nextId();
136             System.out.println(Long.toBinaryString(id));
137             System.out.println(id);
138         }
139     }
140 }