推特(Twitter)的Snowflake算法——用于生成唯一ID

1.前言

  关于如何在系统中生成唯一性ID的问题(如订单号、批次号等),一直困扰了许久。因为还要考虑并发的问题,所以时间戳+随机数的组合并不可取,Java中的UUID是一种可取的方法,但它的缺点是序列号太长了,而且没有可读性,对用户来说这么一堆乱码是极不友好的。

  推特的工程师snowflake也提出了一个在分布式系统中生成唯一序列的方法。SnowFlake的优点是,效率高,整体上按照时间自增排序,提高了序列号的可读性,对用户来说也比较友好,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)。

 2.SnowFlake算法的Java实现

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

输出序列号示例:

412992501465481216
412992501465481217
412992501465481218
412992501465481219
412992501465481220
412992501465481221
412992501465481222

......

参考:https://github.com/twitter/snowflake

posted @ 2018-02-13 15:52  Jakeylove3  阅读(7041)  评论(0编辑  收藏  举报