https://blog.csdn.net/wangming520liwei/article/details/80843248

ID 生成器 雪花算法

2018年06月28日 14:58:43 wangxiaoming 阅读数:928
 

我们的业务需求中通常有需要一些唯一的ID,来记录我们某个数据的标识:

  • 某个用户的ID

  • 某个订单的单号

  • 某个信息的ID

看图理解

详细的看代码注释

  • 1bit:一般是符号位,不做处理

  • 41bit:用来记录时间戳,这里可以记录69年,如果设置好起始时间比如今年是2018年,那么可以用到2089年,到时候怎么办?要是这个系统能用69年,我相信这个系统早都重构了好多次了。

  • 10bit:10bit用来记录机器ID,总共可以记录1024台机器,一般用前5位代表数据中心,后面5位是某个数据中心的机器ID

  • 12bit:循环位,用来对同一个毫秒之内产生不同的ID,12位可以最多记录4095个,也就是在同一个机器同一毫秒最多记录4095个,多余的需要进行等待下毫秒。

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

因为机器的原因会发生时间回拨,我们的雪花算法是强依赖我们的时间的,如果时间发生回拨,有可能会生成重复的ID

普通的算法会直接抛出异常,这里我们可以对其进行优化,一般分为两个情况:

  • 如果时间回拨时间较短,比如配置5ms以内,那么可以直接等待一定的时间,让机器的时间追上来。

  • 如果时间的回拨时间较长,我们不能接受这么长的阻塞等待,那么又有两个策略:

  1. 直接拒绝,抛出异常,打日志,通知RD时钟回滚。

  2. 利用扩展位,上面我们讨论过不同业务场景位数可能用不到那么多,那么我们可以把扩展位数利用起来了,比如当这个时间回拨比较长的时候,我们可以不需要等待,直接在扩展位加1。2位的扩展位允许我们有3次大的时钟回拨,一般来说就够了,如果其超过三次我们还是选择抛出异常,打日志。

ID 生成器 雪花算法的更多相关文章

  1. 唯一ID生成器--雪花算法

    在微服务架构,分布式系统中的操作会有一些全局性ID的需求,所以我们不能用数据库本身的自增功能来产生主键值,只能由程序来生成唯一的主键值.我们采用的是twitter的snokeflake(雪花)算法. ...

  2. 生成主键ID,唯一键id,分布式ID生成器雪花算法代码实现

    工具类:  package com.ihrm.common.utils; import java.lang.management.ManagementFactory; import java.net. ...

  3. ID生成 雪花算法

    /** * ID生成 雪花算法 */ public class SnowFlake { public static SnowFlake getInstance() { return Singleton ...

  4. 分布式ID的雪花算法及坑

    分布式ID生成是目前系统的常见刚需,其中以Twitter的雪花算法(Snowflake)比较知名,有Java等各种语言的版本及各种改进版本,能生成满足分布式ID,返回ID为Long长整数 但是这里有一 ...

  5. 全局ID生成--雪花算法

    分布式ID常见生成策略: 分布式ID生成策略常见的有如下几种: 数据库自增ID. UUID生成. Redis的原子自增方式. 数据库水平拆分,设置初始值和相同的自增步长. 批量申请自增ID. 雪花算法 ...

  6. 适用于分布式ID的雪花算法

    基于Java实现的适用于分布式ID的雪花算法工具类,这里存一下日后好找 /** * 雪花算法生成ID */ public class SnowFlakeUtil { private final sta ...

  7. 分布式ID生成 - 雪花算法

    雪花算法是一种生成分布式全局唯一ID的经典算法,关于雪花算法的解读网上多如牛毛,大多抄来抄去,这里请参考耕耘的小象大神的博客ID生成器,Twitter的雪花算法(Java) 网上的教程一般存在两个问题 ...

  8. 生成ID之雪花算法

    package com.shopping.test; /** * SnowFlake的结构如下(每部分用-分开):<br> * 0 - 0000000000 0000000000 0000 ...

  9. 全局ID生成--雪花算法改进版

    存在的问题 时间回拨问题:由于机器的时间是动态的调整的,有可能会出现时间跑到之前几毫秒,如果这个时候获取到了这种时间,则会出现数据重复 机器id分配及回收问题:目前机器id需要每台机器不一样,这样的方 ...

随机推荐

  1. SQL Server用表组织数据

    一.主键 主键作为表中的唯一标识,标识这一列不允许出现重复数据    如果两列或多列组合起来唯一标识表中的每一行,该主键叫“复合主键” 选择主键的原则     最少性      尽量选择单个键作为主键 ...

  2. MyBatis最原始的实现curd的操作

    关于jdbc的缺点: 1.数据库链接创建释放频繁造成系统资源浪费从而影响系统性能.如果使用数据库连接池可以解决此问题. 2.sql语句在代码中硬编码,不利于维护,sql变动需要改变java代码 3.使 ...

  3. js 音乐播放器

    在写之前先说下我遇到得两个问题,第一个问题是,在音乐标签,我希望得是切换数据做到得,但是出了问题,暂时为解决,第二个问题,页面切换时音乐继续播放由卡顿情况,未处理好. 好了,那我们开始做这个音乐播放器 ...

  4. 精进之路之CAS

    CAS (Compare And Swap) 即比较交换, 是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术,本文将深入的介绍CAS的原理. 其算法核心思想如下 执行函数: ...

  5. 怎样检测TCP/UDP端口的连通性

    1 TCP端口的连通性 TC端口的连通性,一般通过telnet检测: TCP协议是面向连接的,可以直接通过telnet命令连接 telnet host-ip port 2 UDP端口的连通性 因为公司 ...

  6. python爬虫初级--获取指定页面上的菜单名称以及链接,然后导出

    ''' Created on 2017年4月5日 @author: Admin ''' import requests from bs4 import BeautifulSoup as bsp # 网 ...

  7. jQuery-4.动画篇---jQuery核心

    jQuery中each方法的应用 jQuery中有个很重要的核心方法each,大部分jQuery方法在内部都会调用each,其主要的原因的就是jQuery的实例是一个元素合集 如下:找到所有的div, ...

  8. Alienware R8外星人台式机安装双系统(WIN10+Ubuntu)的总结

    新电脑终于到了,然而外星人的系统比较特殊,很多东西和别的品牌(包括DELL)不一样, 同时NVIDIA显卡也带来了很多问题.重装了十几遍,查阅了上百篇文章后之后终于搞定了双系统. 其实核心问题很傻,就 ...

  9. HDU - 5755:Gambler Bo (开关问题,%3意义下的高斯消元)

    pro:给定N*M的矩阵,每次操作一个位置,它会增加2,周围4个位置会增加1.给定初始状态,求一种方案,使得最后的数都为0:(%3意义下. sol:(N*M)^3的复杂度的居然过了.          ...

  10. 列表中使用嵌套for循环[i*j for i in range(3) for j in range(3)]

    利用嵌套for循环形成一个新列表 [i*j for i in range(3) for j in range(3)]相当于如下代码 li=[] for i in range(3): for j in ...