雪花ID的简介讲解和应用

雪花ID

一、UUID

UUID(Universally Unique Identifier,通用唯一识别码)是按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片 ID 码和许多可能的数字。

UUID 是由一组 32 位数组成,由16 进制数字所构成,是故 UUID 理论上的总数为16的32次方。这个总数是多大呢?打个比方,如果每纳秒产生 1 百万个 UUID,要花 100 亿年才会将所有 UUID 用完。

UUID 通常以连字号分隔的五组来显示,形式为 8-4-4-4-12,总共有 36 个字符(即 32 个英数字母和 4 个连字号)。例如: 123e4567-e89b-12d3-a456-426655440000

JDK 从 1.5 开始在 java.util 包下提供了一个 UUID 类用来生成 UUID:

UUID uuid = UUID.randomUUID();
String uuidStr1 = uuid.toString();
String uuidStr2 = uuidStr1.replaceAll("-","");

1、UUID 的缺点和一个『好』ID 的标准

UUID的缺点:

为了得到一个全局唯一 ID,很自然地就会想到 UUID 算法。但是,UUID 算法有明显的缺点:

  • UUID 太长了,通常以 36 长度的字符串表示,很多场景不适用。

  • 非纯数字。UUID 中会出现 ABCDEF 这些十六进制的字母,因此,在数据库和代码中,自然就不能存储在整型字段或变量。因此,在数据库中以它作为主键,建立索引的代价比较大,性能有影响。

  • 不安全。UUID 中会包含网卡的 MAC 地址。

一个『好』ID 的标准应该有哪些:

  • 最好是由纯数字组成。

  • 越短越好,最好能存进整型变量和数据库的整型字段中。

  • 信息安全。另外,『ID 连续』并非好事情。

  • 在不连续的情况下,最好是递增的。即便不是严格递增,至少也应该是趋势递增。

二、Twitter 的雪花算法(SnowFlake)

Snowflake 是 Twitter(美国推特公司)开源的分布式 ID 生成算法。最初 Twitter 把存储系统从 MySQL 迁移到 Cassandra(它是NoSQL数据库),因为Cassandra 没有顺序 ID 生成机制,所以 Twitter 开发了这样一套全局唯一 ID 生成服务。

SnowFlake 优点:

整体上按照时间自增排序,并且整个分布式系统内不会产生 ID 碰撞(由数据中心 ID 和机器 ID 作区分),并且效率较高。经测试,SnowFlake 每秒能够产生 26 万 ID 左右。

Snowflake 会生成一个 long 类型的数值,long是8个字节,一共是64位,Snowflake 对于 long 的各个位都有固定的规范:

在这里插入图片描述

  • 最高位标识(1 位)

    由于 long 基本类型在 Java 中是带符号的,最高位是符号位,正数是 0,负数是 1,因为 id 一般是正数,所以最高位是 0 。

  • 毫秒级时间戳(41 位)

    注意,41 位时间戳不是存储当前时间的时间戳,而是存储时间的差值(当前时间戳 - 开始时间戳) 得到的值,这里的的开始时间,一般是我们的 id 生成器开始使用的时间,由我们程序来指定的(如下面程序 IdGenerator 类的 startTime 属性)。

    41 位的时间截,可以使用 69 年。

    2的41次方 除以 (1000毫秒 * 60 * 60 * 24 * 365) = 69

  • 数据机器位(10 位)

    10-bit机器可以分别表示1024台机器,这 10 位的机器位实际上是由 5 位的 互联网数据中心(datacenterId) 和 5 位的工作机器id(workerId) 。这样就可以有32个互联网数据中心(机房)(2的5次方),每个互联网数据中心可以有32台工作机器 。即,总共允许存在 1024 台电脑各自计算 ID 。

    每台电脑都由 data-center-id 和 worker-id 标识,逻辑上类似于联合主键的意思。

  • 12位的自增序列号,用来记录同毫秒内产生的不同id,就是一毫秒内最多可以产生4096个id

    毫秒内的计数,12为的自增序列号 支持每个节点每毫秒(同一机器,同一时间截)产生 4096(2的12次方) 个 ID 序号,这种分配方式可以保证在任何一个互联网数据中心的任何一台工作机器在任意毫秒内生成的ID都是不同的

面试常问:如果是并发量高,同一台机器一毫秒有5000个id,那么id会不会重复,不会,根据源码如果一毫秒内超过4096个id,则会阻塞到下一毫秒再生成

1、Snowflake 实现源码

public class SnowflakeIdGenerator {// ==============================Fields===========================================// 所占位数、位移、掩码/极大值private static final long sequenceBits = 12;  //序列号占用位数private static final long workerIdBits = 5;  //工作机器占用位数private static final long dataCenterIdBits = 5;  //数据中心占用位数(机房)//~表示非,例如 01 的非  10     负数的二进制 = 该正数的二进制取反+1//为什么不直接写4095呢?(主要计算机运算的时候是二进制,如果写4095的话,还是要转二进制,效率低)private static final long sequenceMask = ~(-1L << sequenceBits);  //4095  (0到4095 刚好是4096个)  private static final long workerIdShift = sequenceBits; //12 private static final long workerIdMask = ~(-1L << workerIdBits); //31private static final long dataCenterIdShift = sequenceBits + workerIdBits;  //17private static final long dataCenterIdMask = ~(-1L << dataCenterIdBits); //31 private static final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;//22//private static final long timestampBits = 41L; //private static final long timestampMask = ~(-1L << timestampBits);//2199023255551/*** 开始时间截 (2015-01-01)  1420070400000L/1000/60/60/24/30/12 = 25+1970 = 2015-01-01*/private static final long twepoch = 1420070400000L;private long sequence = 0;  //序列号private long workerId;      //工作机器标识private long dataCenterId;  //数据中心private long lastTimestamp = -1L; //上次生成 ID 的时间截//==============================Constructors=====================================public SnowflakeIdGenerator() {this(0, 0);}/*** 构造函数** @param workerId     工作ID (0~31)* @param dataCenterId 数据中心 ID (0~31)*/public SnowflakeIdGenerator(long workerId, long dataCenterId) {if (workerId > workerIdMask || workerId < 0) {throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", workerIdMask));} this.workerId = workerId;this.dataCenterId = dataCenterId;}// ============================== Methods ==========================================/*** 获得下一个 ID (该方法是线程安全的,synchronized)*/public synchronized long nextId() {long timestamp = timeGen(); //获取当前服务器时间// 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常。// 出现这种原因是因为系统的时间被回拨,或出现闰秒现象。// 你也可以不抛出异常,而是调用 tilNextMillis 进行等待if (timestamp < lastTimestamp) {Thread.sleep(3000)timestamp =   timeGen();if(timestamp < lastTimestamp){throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}// 如果是同一时间生成的,则时并发量高的情况下,同一毫秒内最大支持4096个id,否则阻塞到下一秒生成if (lastTimestamp == timestamp) {// 相同毫秒内,序列号自增  , sequence = 4095时, 0 = (sequence + 1) & sequenceMasksequence = (sequence + 1) & sequenceMask;  // 毫秒内序列溢出,即,同一毫秒的序列数已经达到最大if (sequence == 0) {// 阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}// 时间戳改变,毫秒内序列重置else {sequence = 0L;}// 将当前生成的时间戳记录为『上次时间戳』。『下次』生成时间戳时要用到。lastTimestamp = timestamp;// 移位并通过或运算拼到一起组成 64 位的 ID = 8个字节return ((timestamp - twepoch) << timestampLeftShift) // 时间毫秒数左移22位| (dataCenterId << dataCenterIdShift) //数据中心节点左移17位| (workerId << workerIdShift) // 机器节点左移12位| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳** @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 阻塞到下一个毫秒,直到获得新的时间戳** @param timestamp 当前时间错* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long timestamp, long lastTimestamp) {while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间** @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}//==============================Test=============================================/*** 测试*/public static void main(String[] args) {System.out.println(System.currentTimeMillis());SnowflakeIdGenerator idWorker = new SnowflakeIdGenerator(1, 1);long startTime = System.nanoTime();for (int i = 0; i < 50000; i++) {long id = idWorker.nextId();System.out.println(id);}System.nanoTime(); //获取当前纳秒System.out.println((System.nanoTime() - startTime) / 1000000 + "ms");}
}

可以把上面的方法封装厂成工具类

在某个service的实现类里直接调用

SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator(1, 1);
//获取雪花id
long SnowflakeId = snowflakeIdGenerator.nextId();

2、解决时间回拨问题

原生的 Snowflake 算法是完全依赖于时间的,如果有时钟回拨的情况发生,会生成重复的 ID,市场上的解决方案也是不少。简单粗暴的办法有:

  • 采用直接抛异常方式:上面就是这种方式,虽然可行,但是这种很不友好,太粗暴
  • 使用阿里云的的时间服务器和自己的服务器进行同步,2017 年 1 月 1 日的闰秒调整,阿里云服务器 NTP 系统 24 小时“消化”闰秒,完美解决了问题。

    [root@localhost ~]# ntpdate ntp1.aliyun.com
    
  • 如果发现有时钟回拨,时间很短比如 3 毫秒(一般大于3毫秒就不建议等待),就等待(线程睡3秒再来生成id),然后再生成。

 public synchronized long nextId() {long timestamp = timeGen(); //获取当前服务器时间if (timestamp < lastTimestamp) {Thread.sleep(3000)timestamp =   timeGen();if(timestamp < lastTimestamp){throw new RuntimeException(String.format("Clock moved backw ....", lastTimestamp - timestamp));}}......}
  • 集群:如某台服务器准备一个备机或者多个备机,当主服务器出现异常情况时,可以选择备机

三、java位运算符

1、与运算符(&)

如果 4&7 那么这个应该怎么运算呢?在这里要提到一点,1表示true,0表示false,在做与运算的时候,规则如下:

上下都为1则结果为1
上下有一个为0则结果为0

案例:4&7

在这里插入图片描述

2、或运算符(|)

在做或运算的时候,规则如下:

上下有一个为1则结果为1
上下都为0则结果为0

案例: 5|9

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nt8QlA3N-1691408748928)(assets\image-20220211165012586.png)]

3、异或运算符(^)

在异或的时候,上下都为1则结果为0,下下有一个为0则结果为1

案例:7^15

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SjOl13Ez-1691408748928)(assets\image-20220211165316835.png)]

4、取反运算符(~)

这个其实挺简单的,就是把1变0,0变1,但是注意:二进制中,最高位是符号位 1表示负数,0表示正数

案例:15取反

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oGERByig-1691408748929)(assets\image-20220211165831898.png)]

说明:15的原码为:0000 1111,因为正数的原码=反码=补码,在cpu真正存储的时候都是存补码的,要对原码取反,就要先得到补码,然后对补码取反,所以也就是对15的补码进行取反,取反之后变成 1111 0000,变成的1111 0000 还是补码,那么怎么从补码转换为我们能认识的原码呢?其实很简单,首先把补码1111 0000 取反,符号位不变,然后+1,就可以得到原码

1、1111 0000 取反为 1000 1111

2、1000 1111

​ 0000 0001


​ 1000 1000 = -16

类似的~9 = -10,~7 = -8,5=-6,-16=15,~-8=7 ,~-10=9.发现什么规律了吗?

5、左移运算(<<)

左移就是把所有位向左移动几位

案例:12 << 2 就是12向左移动两位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q7xxoNaB-1691408748929)(assets\image-20220211173405835.png)]

通过这个图我们可以看出来,所有的位全都向左移动两位,然后把右边空的两个位用0补上,最左边多出的两个位去掉,最后得到的结果就是00110000 结果就是48,我们用同样的办法算 12<<3 结果是 96 。由此我们得出一个快速的算法 M << n 其实可以这么算 M << n = M * 2的n次方

6、右移运算符(>>)

这个跟左移运算大体是一样的

案例: 12 >> 2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W9FFjZhT-1691408748929)(assets\image-20220211173839914.png)]

我们可以看出来右移和左移其实是一样的,但是还是有点不同的,不同点在于对于正数和负数补位的时候补的不一样,负数补1,正数补0
如我们再做一个 –8 的 -8>>2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tuqdfb4V-1691408748930)(assets\image-20220211175250189.png)]

这里总结一下,关于负数或者正数来说,移位的时候是一样的,但是在补位的时候,如果最高位是0就补0,如果最高位是1就补1
由此我们得出一个快速的算法 M >> n 其实可以这么算 M >> n = M / 2^n

上面说过,cpu计算的一个数的时候都是得到这个数的补码,然后计算

对于正数而言:原码 = 反码 = 补码

对于负数而言:补码 = 原码取反 + 1

如:-8的原码是:1000 1000 转成补码是 1(符号位不变)111 0111 ,然后+1,变成 1111 1000

正数的补码=源码

已知一个负数的补码,如何求源码?补码的补码 = 源码

如:负数的补码为1111 1110,它的源码为1(符号位不变) 0 0 0 0 0 0 1 + 1 = -2

7、无符号右移(>>>)

无符号右移(>>>)只对32位和64位有意义

在移动位的时候与右移运算符的移动方式一样的,区别只在于补位的时候不管是0还是1,都补0

四、mybatis plus实现雪花id

mybatis-plus已经内置雪花算法生成分布式唯一id。在mybatis-plus特性中已经明确说明了这点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzE91YGk-1691408748930)(assets\image-20221108101202859.png)]

我们可以直接在IDEA中双击shift搜索Sequence类查看其具体实现,可以发现其实现就是采用了雪花算法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-63NtFq4D-1691408748930)(assets\image-20221108101744578.png)]

  • 修改实体类

    @Data
    @EqualsAndHashCode(callSuper = false)
    @NoArgsConstructor
    @AllArgsConstructor
    @TableName("rbac_user")
    public class UserPo implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.ASSIGN_ID)  //注意看这里,注意看这里private Long id;private String username;private String password;private boolean status;  //true:正常   false:锁定private String email;
    }
    

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/79524.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【状态估计】基于UKF法、AUKF法的电力系统三相状态估计研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Pycharm连接服务器

前提&#xff1a;必须为pycharm专业版才能连接到服务器 以下为pycharm2023专业版 一、连接 系统环境 虚拟环境&#xff08;前提&#xff1a;已安装anaconda&#xff09; (1) anaconda环境 (2) 自己创建的虚拟环境 这里为envs下的spotr 二、查看连接情况 选择自动上传

多语言一键铺货跨境电商平台快速开发(java开源)

要搭建一个多语言一键铺货跨境电商平台&#xff0c;可以参考以下步骤&#xff1a; 1. 确定需求&#xff1a;首先&#xff0c;明确平台的功能需求&#xff0c;包括多语言支持、一键铺货功能、跨境支付等。 2. 选择适合的开源项目&#xff1a;选择一个适合的Java开源电商平台项…

Fabric

Fabric Fabric.js是一个非常好用的Javascript HTML5 canvas库&#xff0c;封装了canvas原生较为复杂的api&#xff0c;在canvas元素的顶部提供交互式对象模型&#xff0c;用于实现图片的变形旋转拖拉拽等功能。 在线demo: 官网链接 下载 npm install fabric --save或 yarn …

【云原生K8s】二进制部署单master K8s+etcd集群

一、实验设计 mater节点master01192.168.190.10kube-apiserver kube-controller-manager kube-scheduler etcd node节点node01192.168.190.20kubelet kube-proxy docker (容…

Matlab之利用MarkerFaceColor来填充marker

matlab画图在加一些marker的时候, 有实心的圆圈, 比如: plot(x,y,.r,MarkerSize,20)但是如果想要一个很大的marker, 就需要把这个markersize调得很大, 比如MarkerSize20 但是也可以用空心的圆圈然后把中间涂上颜色, 这样调整起来更方便. 比如: plot(x,y,or,MarkerSize,5,Mar…

避免安装这5种软件,手机广告频繁弹窗且性能下降

在我们使用手机的日常生活中&#xff0c;选择合适的应用软件对于保持良好的使用体验至关重要。然而&#xff0c;有些软件可能会给我们带来不必要的麻烦和困扰。特别是那些频繁弹窗广告、导致手机性能下降的应用程序&#xff0c;我们应该尽量避免安装它们。 首先第一种&#xf…

Embedding入门介绍以及为什么Embedding在大语言模型中很重要

Embeddings技术简介及其历史概要 在机器学习和自然语言处理中&#xff0c;embedding是指将高维度的数据&#xff08;例如文字、图片、音频&#xff09;映射到低维度空间的过程。embedding向量通常是一个由实数构成的向量&#xff0c;它将输入的数据表示成一个连续的数值空间中…

Python Opencv实践 - 基本图像IO操作

import numpy as np import cv2 as cv import matplotlib.pyplot as plt#读取图像 #cv2.IMREAD_COLOR&#xff1a; 读取彩色图像&#xff0c;忽略alpha通道&#xff0c;也可以直接写1 #cv2.IMREAD_GRAYSCALE: 读取灰度图&#xff0c;也可以直接写0 #cv2.IMREAD_UNCHANGED: 读取…

推荐一个OI的维基百科网站

推荐一个关于OI的维基百科网站&#xff1a; https://oi-wiki.org/ 链接: OI Wiki 这里面有很多关于竞赛的知识&#xff0c;还有各种讲解哦&#xff01;&#xff01;&#xff01; 当然&#xff0c;里面要是有什么看不懂的也可以问我哦&#xff01;&#xff01;&#xff01;

【MySQL】聚合函数与分组查询

文章目录 一、聚合函数1.1 count 返回查询到的数据的数量1.2 sum 返回查询到的数据的总和1.3 avg 返回查询到的数据的平均值1.4 max 返回查询到的数据的最大值1.5 min 返回查询到的数据的最小值 二、分组查询group by2.1 导入雇员信息表2.2 找到最高薪资和员工平均薪资2.3 显示…

【网络编程】利用套接字实现一个简单的网络通信(UDP实现聊天室 附上源码)

网络编程套接字 &#x1f41b;预备知识&#x1f98b;理解源IP地址和目的IP地址&#x1f40c;认识端口号&#x1f41e; 理解 "端口号" 和 "进程ID"&#x1f41c;简单认识TCP协议&#x1f99f;简单认识UDP协议&#x1f997; 什么是网络字节序 &#x1f577;相…

Session与Cookie的区别(五)

储存状态的方式 小明的故事说完了&#xff0c;该来把上面这一段变成网络的实际案例了。其实在网络世界中问题也是一样的。 前面已经提到过我们会把状态存在 Cookie 里面&#xff0c;让 Request 之间能够变得有关联。 假设我们今天要来做一个会员系统&#xff0c;那我要怎么知道…

解读百胜中国2023年第二季度财报:聚焦下沉市场,扩店实力几何?

从全网玩梗的“肯德基疯狂星期四”文学&#xff0c;到大小朋友疯狂抢购的六一三丽鸥玩具联名&#xff0c;再到不久前爆火的必胜客原神联名活动&#xff0c;肯德基、必胜客这两大家喻户晓的快餐品牌&#xff0c;被不少新闻调侃为“顶流制造机”。而近日&#xff0c;这两大顶流背…

Day 75:通用BP神经网络 (2. 单层实现)

代码&#xff1a; package dl;import java.util.Arrays; import java.util.Random;/*** Ann layer.*/ public class AnnLayer {/*** The number of input.*/int numInput;/*** The number of output.*/int numOutput;/*** The learning rate.*/double learningRate;/*** The m…

海外社媒营销:如何树立品牌个性与目标受众共鸣?

随着全球化的不断深入&#xff0c;海外市场对于企业的重要性越来越凸显。在这个数字化时代&#xff0c;社交媒体已经成为品牌塑造和推广的重要渠道之一。然而&#xff0c;海外市场竞争激烈&#xff0c;想要在众多品牌中脱颖而出&#xff0c;就需要在社交媒体关注者的心中树立品…

AcWing1171. 距离(lcatarjan)

输入样例1&#xff1a; 2 2 1 2 100 1 2 2 1输出样例1&#xff1a; 100 100输入样例2&#xff1a; 3 2 1 2 10 3 1 15 1 2 3 2输出样例2&#xff1a; 10 25 #include<bits/stdc.h> using namespace std; typedef long long ll; const int N2e55; int n,m,x,y,k,r…

算法通关村—轻松搞定二叉树的高度和深度问题

1.二叉树的最大深度 二叉树的最大深度 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 1.1 递归 通过上面的步骤能够看出&#xff0c;深度取决于左右子树&#xff0c;只要左子树有&#xff0c;那么高…

gateway过滤器没生效,特殊原因

看这边文章的前提&#xff0c;你要会gateway&#xff0c;知道过滤器怎么配置&#xff1f; 直接来看过滤器&#xff0c;局部过滤器 再来看配置 请求路径 http://127.0.0.1:8080/appframework/services/catalog/catalogSpecials.json?pageindex1&pagesize10&pkidd98…

php-cgi.exe - FastCGI 进程超过了配置的请求超时时限

解决方案一&#xff1a; 处理(php-cgi.exe - FastCGI 进程超过了配置的请求超时时限)的问题 内容转载&#xff1a; 处理(php-cgi.exe - FastCGI 进程超过了配置的请求超时时限)的问题_php技巧_脚本之家 【详细错误】&#xff1a; HTTP 错误 500.0 - Internal Server Error C:…