1、算法

SnowFlake算法生成的数据组成结构如下:

在java中用long类型标识,共64位(每部分用-分开):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000 00

  • 1位标识,0表示正数。
  • 41位时间戳,当前时间的毫秒减去开始时间的毫秒数。可用 (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年。
  • 5位数据中心标识,可支持(1L << 5) = 32个数据中心。
  • 5位机器标识,每个数据中心可支持(1L << 5) = 32个机器标识。
  • 12位序列号,每个节点每一毫秒支持(1L << 12) = 4096个序列号。

2、Java版本实现


/**
* 雪花算法<br>
* 在java中用long类型标识,共64位(每部分用-分开):<br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000 00<br>
* 1位标识,0表示正数。<br>
* 41位时间戳,当前时间的毫秒减去开始时间的毫秒数。可用 (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年。<br>
* 5位数据中心标识,可支持(1L << 5) = 32个数据中心。<br>
* 5位机器标识,每个数据中心可支持(1L << 5) = 32个机器标识。<br>
* 12位序列号,每个节点每一毫秒支持(1L << 12) = 4096个序列号。<br>
*/
public class SnowflakeIdWorker {
/**
* 机器标识
*/
private long workerId;
/**
* 数据中心标识
*/
private long dataCenterId;
/**
* 序列号
*/
private long sequence;
/**
* 机器标识占用5位
*/
private long workerIdBits = 5L;
/**
* 数据中心标识占用5位
*/
private long dataCenterIdBits = 5L;
/**
* 12位序列号
*/
private long sequenceBits = 12L; /**
* 12位序列号支持的最大正整数
* ....... 00001111 11111111
* 2^12-1 = 4095
*/
private long sequenceMask = ~(-1L << sequenceBits); /**
* The Worker id shift.
* 12位
*/
private long workerIdShift = sequenceBits;
/**
* The Data center id shift.
* 12 + 5 = 17位
*/
private long dataCenterIdShift = sequenceBits + workerIdBits;
/**
* The Timestamp shift.
* 12 + 5 + 5 = 22位
*/
private long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;
/**
* 开始时间戳毫秒
*/
private long startEpoch = 29055616000L;
/**
* The Last timestamp.
*/
private long lastTimestamp = -1L; public SnowflakeIdWorker(long workerId, long dataCenterId, long sequence) {
// 检查workerId是否正常
/*
机器标识最多支持的最大正整数
-1的补码:
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
-1 左移 5 位,高位溢出,低位补0:
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000
取反:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011111
转10进制:
16 + 8 + 4 + 2 + 1 = 31
*/
long maxWorkerId = ~(-1L << workerIdBits);
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("工作Id不能大于%d或小于0", maxWorkerId));
} /*
数据中心最多支持的最大正整数31
*/
long maxDataCenterId = ~(-1L << dataCenterIdBits);
if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
throw new IllegalArgumentException(String.format("数据中心Id不能大于%d或小于0", maxDataCenterId));
} this.workerId = workerId;
this.dataCenterId = dataCenterId;
this.sequence = sequence;
} private synchronized long nextId() {
//获取当前时间毫秒数
long timestamp = timeGen(); //如果当前时间毫秒数小于上一次的时间戳
if (timestamp < lastTimestamp) {
System.err.printf("时钟发生回调,拒绝生成ID,直到: %d.", lastTimestamp);
throw new RuntimeException(String.format("时钟发生回调, 拒绝为 %d 毫秒生成ID。",
lastTimestamp - timestamp));
} //当前时间毫秒数与上次时间戳相同,增加序列号
if (lastTimestamp == timestamp) {
//假设sequence=4095
//(4095 + 1) & 4095
//4096: ....... 00010000 00000000
//4095: ....... 00001111 11111111
// ....... 00000000 00000000
//最终sequence为0,即sequence发生溢出。
sequence = (sequence + 1) & sequenceMask;
//如果发生序列号为0,即当前毫秒数的序列号已经溢出,则借用下一毫秒的时间戳
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
//当前毫秒数大于上次的时间戳,序列号为0
sequence = 0;
} //更新
lastTimestamp = timestamp; //生成ID算法,左移几位,则后面加几个0。
//1、当前时间的毫秒数-开始时间的毫秒数,结果左移22位
// 假设:timestamp - startEpoch = 1
// 二进制:
// 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
// 左移22位:
// 00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000
//2、dataCenterId左移17位
// 假设:dataCenterId = 1
// 二进制:
// 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
// 左移17位:
// 00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000 //3、workerId左移12位
// 假设:workerId = 1
// 二进制:
// 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
// 左移12位:
// 00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000
//4、最后的所有结果按位`或`
//假设:sequence = 1
//00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000
//00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000
//00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000
//00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
//00000000 00000000 00000000 00000000 00000000 01000010 00010000 00000001
//结果: 0 - 0000000 00000000 00000000 00000000 00000000 01 - 00001 - 00001 - 0000 00000001 return ((timestamp - startEpoch) << timestampShift) |
(dataCenterId << dataCenterIdShift) |
(workerId << workerIdShift) |
sequence;
} /**
* 获取下一秒
*
* @param lastTimestamp the last timestamp
* @return the long
*/
private long tilNextMillis(long lastTimestamp) {
//获取当前毫秒数
long timestamp = timeGen();
//只要当前的毫秒数小于上次的时间戳,就一直循环,大于上次时间戳
while (timestamp <= lastTimestamp) {
//获取当前毫秒数
timestamp = timeGen();
}
return timestamp;
} /**
* 获取当前毫秒数
*
* @return the long
*/
private long timeGen() {
return System.currentTimeMillis();
} public static void main(String[] args) {
SnowflakeIdWorker worker = new SnowflakeIdWorker(1, 1, 1);
for (int i = 0; i < 10000; i++) {
long id = worker.nextId();
System.out.println(id);
System.out.println(Long.toString(id).length());
System.out.println(Long.toBinaryString(id));
System.out.println(Long.toBinaryString(id).length());
}
} }

3、难点

Tips: 左移几位,则后面加几个0。

3.1、计算机器标识最多支持的最大正整数

private long workerIdBits = 5L;
long maxWorkerId = ~(-1L << workerIdBits);

计算过程:

  • -1的补码:

    11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
  • -1 左移 5 位,高位溢出,低位补0:

    11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000
  • 取反:

    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011111
  • 转10进制:

    16 + 8 + 4 + 2 + 1 = 31

3.2、sequence溢出处理

sequence = (sequence + 1) & sequenceMask;

计算过程:

假设sequence=4095:

  • (4095 + 1) & 4095
  • 4096: ....... 00010000 00000000
  • 4095: ....... 00001111 11111111
  • 按位与 ....... 00000000 00000000
  • 最终sequence为0,即sequence发生溢出。

3.3、ID计算

((timestamp - startEpoch) << timestampShift) |
(dataCenterId << dataCenterIdShift) |
(workerId << workerIdShift) |
sequence

计算过程:

  • 当前时间的毫秒数-开始时间的毫秒数,结果左移22位

假设:timestamp - startEpoch = 1

二进制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

左移22位: 00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000

  • dataCenterId左移17位

假设:dataCenterId = 1

二进制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

左移17位: 00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000

  • workerId左移12位

假设:workerId = 1

二进制: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

左移12位: 00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000

  • 最后的所有结果按位

假设:sequence = 1

00000000 00000000 00000000 00000000 00000000 01000000 00000000 00000000

00000000 00000000 00000000 00000000 00000000 00000010 00000000 00000000

00000000 00000000 00000000 00000000 00000000 00000000 00010000 00000000

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

00000000 00000000 00000000 00000000 00000000 01000010 00010000 00000001

  • 结果:

0 - 0000000 00000000 00000000 00000000 00000000 01 - 00001 - 00001 - 0000 00000001

符合SnowFlake算法数据组成结构。

参考

理解分布式id生成算法SnowFlake

Twitter雪花算法SnowFlake算法的java实现


分布式ID方案SnowFlake雪花算法分析的更多相关文章

  1. 分布式ID生成器 snowflake(雪花)算法

    在springboot的启动类中引入 @Bean public IdWorker idWorkker(){ return new IdWorker(1, 1); } 在代码中调用 @Autowired ...

  2. redis生成分布式id方案

    分布式Id - redis方式   本篇分享内容是关于生成分布式Id的其中之一方案,除了redis方案之外还有如:数据库,雪花算法,mogodb(object_id也是数据库)等方案,对于redis来 ...

  3. 大型互联网公司分布式ID方案总结

    ID是数据的唯一标识,传统的做法是利用UUID和数据库的自增ID,在互联网企业中,大部分公司使用的都是Mysql,并且因为需要事务支持,所以通常会使用Innodb存储引擎,UUID太长以及无序,所以并 ...

  4. 分布式ID方案有哪些以及各自的优劣势,我们当如何选择

    作者介绍 段同海,就职于达达基础架构团队,主要参与达达分布式ID生成系统,日志采集系统等中间件研发工作. 背景 在分布式系统中,经常需要对大量的数据.消息.http请求等进行唯一标识,例如:在分布式系 ...

  5. 分布式id的生成方式——雪花算法

    雪花算法是twitter开源的一个算法. 由64位0或1组成,其中41位是时间戳,10位工作机器id,12位序列号,该类通过方法nextID()实现id的生成,用Long数据类型去存储. 我们使用id ...

  6. 分布式ID方案有哪些以及各自的优势

    1.    背景 在分布式系统中,经常需要对大量的数据.消息.http请求等进行唯一标识.例如:在分布式系统之间http请求需要唯一标识,调用链路分析的时候需要使用这个唯一标识.这个时候数据自增主键已 ...

  7. 分布式id生成器,雪花算法IdWorker

    /** * <p>名称:IdWorker.java</p> * <p>描述:分布式自增长ID</p> * <pre> * Twitter的 ...

  8. 自增ID算法snowflake(雪花)

    在数据库主键设计上,比较常见的方法是采用自增ID(1开始,每次加1)和生成GUID.生成GUID的方式虽然简单,但是由于采用的是无意义的字符串,推测会在数据量增大时造成访问过慢,在基础互联网的系统设计 ...

  9. Dubbo学习系列之七(分布式订单ID方案)

    既然选择,就注定风雨兼程! 开始吧! 准备:Idea201902/JDK11/ZK3.5.5/Gradle5.4.1/RabbitMQ3.7.13/Mysql8.0.11/Lombok0.26/Erl ...

随机推荐

  1. 100% 展示 MySQL 语句执行的神器-Optimizer Trace

    在上一篇文章<用Explain 命令分析 MySQL 的 SQL 执行>中,我们讲解了 Explain 命令的详细使用.但是它只能展示 SQL 语句的执行计划,无法展示为什么一些其他的执行 ...

  2. vue中模块局部刷新

    父组件: 一. 父组件中引入子组件           data中定义变量 二. 定义provide函数 三.写reload方法 需要刷新的那个子组件: 一.引入                   ...

  3. 011_go语言中的range遍历

    代码演示 package main import "fmt" func main() { nums := []int{2, 3, 4} sum := 0 for _, num := ...

  4. 您能解决这3个(看似)简单的Python问题吗?

    尝试解决以下问题,然后检查以下答案. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识 ...

  5. GCN 入门

    参考链接: https://www.zhihu.com/question/54504471/answer/611222866 1 拉普拉斯矩阵 参考链接: http://bbs.cvmart.net/ ...

  6. 9、Bridge 桥梁模式 将类的功能层次结构与实现层结构分离 结构型设计模式

    1.何为桥接模式 桥接模式是一种将类的功能层次和实现层次分离的技术,所谓类的功能层次指的是类要实现什么功能,要定义多少个函数还进行处理,在功能之中我们会用到继承来定义新的方法同时也能使用父类的方法,这 ...

  7. .NET Core Web APi大文件分片上传研究

    前言 前两天发表利用FormData进行文件上传,然后有人问要是大文件几个G上传怎么搞,常见的不就是分片再搞下断点续传,动动手差不多也能搞出来,只不过要深入的话,考虑的东西还是很多.由于断点续传之前写 ...

  8. vue_如何判断变量是数组还是对象

    一.typeof判断数据类型(判断数组跟对象都返回object) console.log(typeof null); // "object" console.log(typeof ...

  9. Spring中眼花缭乱的BeanDefinition

    本篇博客主要参考:Spring官网阅读(四)BeanDefinition(上) 引入主题 为什么要读Spring源码,有的人为了学习Spring中的先进思想,也有的人是为了更好的理解设计模式,当然也有 ...

  10. 送大家几个cnblogs号,供快捷评论,请谨慎使用

    欢迎评论& 3461896724@qq.com互动 可以在我的首页看更多 #1先送大家几个号:(密码都是 MLdlight2020)请区分大小写(可以直接复制) 写过一篇 免费验证码接收网站& ...