更多内容,前往IT-BLOG

一、前言


  如何在分布式集群中生产全局唯一的 ID?
【方案一】
UUID:UUID是通用唯一识别码 (Universally Unique Identifier),在其他语言中也叫GUID,可以生成一个长度 32位的全局唯一识别码。UUID 虽然可以保证全局唯一,但是占32位而且无序,入库性能比较差

  1. 1 //例如:44e128a5-ac7a-4c9a-be4c-224b6bf81b20
  2. 2 String uuid = UUID.randomUUID().toString()

举个例子:MySQL 使用 InnoDB 存储引擎,底层索引使用的B+树索引。索引是按照顺序在叶子节点分布的,如果按顺序插入索引,则会使用顺序IO依次写入磁盘,也不会出现页分裂和浪费等问题,性能也比较高。但如果使用无序的 UUID,就会导致磁盘臂不按顺序移动,且会导致页分裂和不饱和的节点导致数据库插入性能降低。
方案二数据库自增主键:在分布式系统中可以用DB proxy请求不同的分库,每个分库设置不同的初始值,步长和分库数量相等。如下:DB1生成的ID是1,4,7,10,13....,DB2生成的ID是2,5,8,11,14.....

这样也不是很好,ID 的生成对数据库严重依赖,不但影响性能,而且一旦数据库挂掉,服务将变的不可用。

二、SnowFlake


SnowFlake 是 Twitter最初把存储系统从 MySQL迁移到 Cassandra时,因为 Cassandra没有顺序ID生成机制,所以开发了这样一套开源的分布式全局唯一的 ID生成算法,结果是一个 long型的ID。这种方案把 64-bit分别划分成多段,分开来标识机器ID、时间等。其核心思想是:使用 41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。SnowFlake  的结构图如下所示:

【1】第一位:1位标识占用1bit,其值始终是0,表示符号位,正数是0,负数是1,id一般是正数,在这里没有实际作用。
【2】第二位:时间戳占用41bit,精确到毫秒,总共可以容纳约69 年的时间。
【3】第三位:工作机器id占用10bit,其中高位5bit是数据中心ID(datacenterId),低位5bit是工作节点ID(workerId),做多可以容纳1024个节点。10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义。
【4】第四位:序列号占用12bit,这个值在同一毫秒同一节点上从0开始不断累加,最多可以累加到4095。12个自增序列号可以表示2^12个ID。
SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢?只需要做一个简单的乘法:同一毫秒的ID数量 = 1024 X 4096 = 4194304(QPS)这个数字在绝大多数并发场景下都是够用的。

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

三、SnowFlake的优势和劣势


【SnowFlake算法的优点】:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
【1】生成ID时不依赖于DB,完全在内存生成,高性能高可用;
【2】ID呈趋势递增,后续插入索引树的时候性能较好;
SnowFlake算法的缺点】:依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序。针对此,美团做出了改进:链接

雪花算法 SnowFlake 内部结构【分布式ID生成策略】的更多相关文章

  1. 分布式ID生成策略 · fossi

    分布式环境下如何保证ID的不重复呢?一般我们可能会想到用UUID来实现嘛.但是UUID一般可以获取当前时间的毫秒数再加点随机数,但是在高并发下仍然可能重复.最重要的是,如果我要用这种UUID来生成分表 ...

  2. 图解Janusgraph系列-分布式id生成策略分析

    JanusGraph - 分布式id的生成策略 大家好,我是洋仔,JanusGraph图解系列文章,实时更新~ 本次更新时间:2020-9-1 文章为作者跟踪源码和查看官方文档整理,如有任何问题,请联 ...

  3. java 雪花算法实现获取分布式id

    import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.NetworkI ...

  4. 分布式ID生成策略之ZK

    import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFra ...

  5. 分布式ID生成策略

    策略一.UUID 策略二.数据库自增序列 策略三.snowflake算法 策略四.基于redis自增 思路:利用增长计数API,业务系统在自增长的基础上,配合其他信息组装成为一个唯一ID. redis ...

  6. Disruptor分布式id生成策略

    需要的pom文件: <!-- 顺序UUID --> <dependency> <groupId>com.fasterxml.uuid</groupId> ...

  7. 业务ID 生成策略

    业务ID 生成策略,从技术上说,基本要借助一个集中式的引擎来帮忙实现. 为了扩大业务ID生成策略的并发问题,还有更为技巧性的提升. 先来介绍普遍的分布式ID生成策略: 1. 利用DB的自增主键 这里又 ...

  8. 分布式ID生成系统 UUID与雪花(snowflake)算法

    Leaf——美团点评分布式ID生成系统 -https://tech.meituan.com/MT_Leaf.html 网游服务器中的GUID(唯一标识码)实现-基于snowflake算法-云栖社区-阿 ...

  9. 一秒可生成500万ID的分布式自增ID算法—雪花算法 (Snowflake,Delphi 版)

    概述 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的. 有些时候我们希望能使用一种 ...

  10. 分布式唯一ID生成方案选型!详细解析雪花算法Snowflake

    分布式唯一ID 使用RocketMQ时,需要使用到分布式唯一ID 消息可能会发生重复,所以要在消费端做幂等性,为了达到业务的幂等性,生产者必须要有一个唯一ID, 需要满足以下条件: 同一业务场景要全局 ...

随机推荐

  1. Mac下如何使用EVE-NG的telnet客户端和wireshark抓包

    当我没有安装SecureCRT,点击启动的设备,弹出使用终端打开,但是由于eve中telnet使用的url是telnet xx.xx.xx.xx:xxxx 的形式,其在终端app中不能正常工作,tel ...

  2. echarts 折柱表混合图(折线,柱状图,表格)

    效果图如下: 代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...

  3. pandas的groupby.apply和直接apply效果是不一样的

    GroupBy.apply(func, *args, **kwargs)[source] Apply function func group-wise and combine the results ...

  4. thymeleaf依赖

    <!--thymeleaf模板--> <dependency> <groupId>org.thymeleaf</groupId> <artifac ...

  5. 【项目记录】4:Pycharm激活方法

    引用一下: 今天给大家带来一种全新的Pycharm安装激活方式.可以激活到2099年. 安装 1.我们首先进入idea官网 jetbrains.com 找到最新版本的pycharm,这里就以windo ...

  6. mysql 循环 例子

    mysql 循环生成数据demo: DROP PROCEDURE IF EXISTS test_insert; DELIMITER;; CREATE PROCEDURE test_insert() B ...

  7. win10 wampserver升级 php7.0至 php7.2

    1.去官网下载php7.2 下载地址: https://windows.php.net/download#php-7.0 2.下载安装 visual c++ 2017 或  visual c++ 20 ...

  8. 解决navicat远程连接MySQL失败,报错1130问题

    1select host from user where user='root'; 2update user set host = '%' where user ='root';3flush priv ...

  9. jetson TX2 + opencv3.4 + python3 + 双目 +人脸检测

    淘宝看到一款很便宜的双目,150元,就买了.想着用它学习一下opencv,好换个工作.当然,也想着能否用它做一些好玩的,比如三维重建之类高大上的东西.先用便宜的入个门,等以后眼界高了再看是不是买那些更 ...

  10. 设计模式 - 单例模式 Singleton Pattern - C#

    单例模式 Singleton Pattern 1.单例模式设计模式属于创建型模式 2.是单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建.这个类提供了一种访问其唯一的对象的方式,可以直接访 ...