更多内容,前往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. Mysql学习:1、mysql安装及配置及连接Navicat

    1.下载地址: https://dev.mysql.com/downloads/windows/installer/8.0.html 2.安装流程: a.选自定义安装:custom. b. 在下一步的 ...

  2. 基础vue的一些知识补充

    一.:disabled 该属性能接受布尔值,可以用于元素的使用.当值为true时,该元素将无法被使用,如button的disabled属性被设置为true后,将无法被点击,input的disabled ...

  3. 参与一个组织npm包的发布

    1.github桌面提交新建一个fork 2.github网站打开fork,new 一个pull request 3.管理员审核成功 npm npm login npm publish --acces ...

  4. 【摘】python和它的内置类型子类化

    python和它的内置类型子类化 看个好玩的东西 class Folder(list): def __init__(self, name): self.name = name def dir(self ...

  5. 【python】第一模块 步骤五 第二课、Python多线程

    第二课.Python多线程 一.课程介绍 1.1 课程概要 章节概要 进程.线程与并发 对多核的利用 实现一个线程 线程之间的通信 线程的调度和优化 1.2 为什么要学习多线程 (线程)使用场景 快速 ...

  6. 数组(Java)

    数组的定义 数组是相同类型数据的有序集合 数组描述的是相同类型的若干数据,按照一定的先后次序排列组合而成 其中,每个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们 数组的声明和创建 基本 ...

  7. Android studio Internet跳转活动

    <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=" ...

  8. Excel如何默认禁用科学计数法?

    微软论坛版主回复"无法默认禁用此功能",可在"设置单元格格式"-"自定义"-"类型"改为"0",去除 ...

  9. 2022竞赛新方法学习1--学习Proceedings of SAT Competition 2022 : Solver and Benchmark Descriptions

    Proceedings of SAT Competition 2022 : Solver and Benchmark Descriptions https://helda.helsinki.fi/ha ...

  10. 01. JavaScript基础知识

    一.JavaScript简介   JavaScript 是一门解释型编程语言,解释型编程语言指代码不需要手动编译,而是通过解释器边解释边执行.所以,要运行 JS,我们需要在计算机中安装 JS 的解释器 ...