zoukankan      html  css  js  c++  java
  • 推特(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

    文章仅供参考,转载请注明出处。
    不怕千万人阻挡,只怕自己投降。
  • 相关阅读:
    Socket_leaks open socket #5024 left in connection
    阿里云 如何减少备份使用量? mysql数据库的完整备份、差异备份、增量备份
    一个正则式引发的血案 贪婪、懒惰与独占
    linux下tmp目录里很多php开头的文件
    后端线上服务监控与报警方案
    架构先行
    数据盘缩容
    文件过滤 批量删除
    mock数据(模拟后台数据)
    如何避免升级 Linux 实例内核后无法启动
  • 原文地址:https://www.cnblogs.com/jakeylove3/p/8446798.html
Copyright © 2011-2022 走看看