更多内容,前往IT-BLOG

一、前言


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

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

    返回项目声明及目录:Leetcode本地阅读器开发--总声明 继续上一节内容: 1.第一个内容是左边第一行的功能,读取默认路径和修改路径.此路径为本地阅读器的搜索题目的主目录. 为了整个程序都能读取工 ...

  2. bzoj 3924

    动态点分治好题 首先我们考虑一个暴力做法: 每次修改之后选一个点作为根搜索整棵树,然后换根dp即可 考虑每次换根时,移向的点的消耗会减少子树代价之和*边权,而其余部分代价会增加剩余代价*边权 这样每次 ...

  3. re模块相关介绍

    re模块 模块和实际工作时间的关系 time模块和时间是什么关系?re模块和正则表达式的关系? 有了re模块就可以在python语言中操作正则表达式 正则表达式 1:什么是正则表达式? 一套规则--- ...

  4. Django框架搭建web项目(三)

    参考官网文档:https://docs.djangoproject.com/zh-hans/4.0/intro/tutorial02/ 在生成的app中进行数据库表设计. 1.在路径H:\myproj ...

  5. 【Android异常】关于Notification启动时,startForeground报错

    遇到两个报错: 第一个权限问题报错,好解决 startForeground requires android.permission.FOREGROUND_SERVICE Manifest给下权限就行 ...

  6. Java基础Day7-值传递和引用传递

    一.值传递 Java都是值传递. 值传递:是指在调用函数时,将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,就不会影响到实际参数. 值传递是对基本数据类型而言. 二.引用传递 引用传递 ...

  7. HBuilder uniapp手机定位

    // 获取当前位置 getPosition: function() { this.GetLocation(function(res) { if (res) { this.longitude = res ...

  8. maven工程入门

    1. 为什么要使用maven? 毕业开始工作,项目组用的maven-spring开发的,不得不了解一下,看过很多介绍,其中maven最大的特点就是 管理jar包和版本管理 (参考:https://ww ...

  9. Python之常用数据类型详解

    tuple 元组 1 # 定义 2 temp = (2, ) # 规范定义,单个元素的元组 3 tem = 2, # 可行,但不规范定义 4 tep = () # 空元组 5 6 tp = (1, ' ...

  10. 2.3Dmax界面_视图调整

    一.试图模型显示效果的切换 '默认是真实显示效果' 线框模式 快捷键F3 ----> 真实显示效果和线框显示效果的切换(切换到线框显示效果再按F3就切换到了真实显示效果). 线面模式 快捷键F4 ...