雪花算法及MP实现方式

根据当前项目推进的情况,我们会发现用户注册时,从来没有考虑主键生成的问题。为什么呢?因为咱们的数据表现在都是通过数据库自增长方式获取主键id的。不过,这个主键自增长的方案好不好呢?我们一起来了解一下程序发展的现状:

  1. 所有的数据表几乎都是采用自增长主键策略。 用户量大,数据库存储数据量变大。海量数据,int作为主键自增,最大值上限问题!

  2. 多数据库服务器的部署模式:分库分表,主键自增长策略有问题!多个拆分子表出现相同的主键值。

解决方案:

排除主键自增,获取唯一特征的序列,常见解决方案:

1 UUID

优点:算法写好,使用简单易入门,全局唯一

作为主键缺点:字符串类型,索引有排序的特征 UUID字符长度过长 UUID算法基于MAC地址

比较好的主键,具备哪些特点,才适合我们数据库主键处理呢?

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

  • 最好是由纯数字组成。
  • 越短越好,最好能存进整型变量和数据库的整型字段中。
  • 信息安全。另外,『ID 连续』并非好事情。
  • 在不连续的情况下,最好是递增的。即便不是严格递增,至少也应该是趋势递增。

Twitter 的雪花算法(SnowFlake)

	Snowflake 是 Twitter(美国推特公司)开源的分布式 ID 生成算法。最初 Twitter 把存储系统从 MySQL 迁移到 Cassandra(它是NoSQL数据库),因为Cassandra 没有顺序 ID 生成机制,所以 Twitter 开发了这样一套全局唯一 ID 生成服务。那为什么要叫雪花算法呢?据相关研究表示,一般的雪花大约由10的19次方个水分子组成。在雪花形成过程中,会形成不同的结构分支,所以说大自然中不存在两片完全一样的雪花,每一片雪花都拥有自己独特的形状。雪花算法的意思是表示生成的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,则会阻塞到下一毫秒再生成

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) {//时间回拨 闰秒//睡3秒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) { //序号位4096个序号用完了// 阻塞到下一个毫秒,获得新的时间戳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");}
}

解决时间回拨问题

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

  • 最简单的方案,就是关闭生成唯一 ID 机器的时间同步。这样做,是不是也不合理,服务器时间好像就跟“标准时间”不一样了

  • 如果发现有时钟回拨,时间很短比如 5 毫秒,就等待,然后再生成。或者就直接报错,交给业务层去处理。也可以采用 SonyFlake 的方案,精确到 10 毫秒,以 10 毫秒为分配单元。

  • 采用直接抛异常方式:上面就是这种方式,虽然可行,但是这种很不友好,太粗暴10秒以内

  • 使用阿里云的的时间服务器和自己的服务器进行同步,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));}}......
    

    }

3 mybatis plus实现雪花id

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

mybatis-plus中主键生成策略有以下几种:
在这里插入图片描述

案例:注册用户时用户id修改为雪花算法

java程序中如何生成雪花ID呢?操作步骤如下所示:

修改数据表的主键类型和主键生成策略
在这里插入图片描述

修改PO对象中主键的生成方式
在这里插入图片描述

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

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

相关文章

SNP过滤

SNP过滤 文章目录 SNP过滤前言一. 利用Perl脚本get_vcf_stats.pl统计位点信息二. 利用R脚本149toTZC.2allele.filtered.R画图并获得过滤后的位点位置信息三. 用vcftools保留过滤后的位点四、get_vcf_stats.pl 脚本存放处 总结 SNP过滤 所属目录&#xff1a;紫菜创建时间&#…

如何查找下载安装安卓APK历史版本?

在安卓设备上&#xff0c;有时候我们可能希望安装某个软件的旧版本&#xff0c;可能是因为新版本不兼容、功能改变不符合需求或是其他原因。 安卓系统并不像iOS那样提供直观的历史版本下载界面。 不过&#xff0c;通过一些第三方市场和网站&#xff0c;我们仍然可以找到并安装…

docker环境下的verdaccio设置权限并配置域名.md

权限配置 一个管理员叫admin,可以读也可以发布一个普通用户叫qiuye,只可以读,不可以发布添加账号就自行创建添加即可,只需要更改config文件的配置项即可 packages:*/*: access: admin qiuyepublish: admin unpublish: admin **:access: admin qiuyepublish: admin unpublish…

Linux——CPU占不上去的解决办法

一、将调节器升至performance&#xff1a; 1.1 查看当前的调节器&#xff1a; cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor如果不是 performance &#xff0c;则进入root账户 1.2 进入root账户 先进入管理员账户输入命令&#xff1a; su root如果没有roo…

【小程序爬虫入门实战】使用Python爬取易题库

文章目录 1. 写在前面2. 抓包分析 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研…

iPhone 在 App Store 中推出的 PC 模拟器 UTM SE

PC 模拟器是什么&#xff1f;PC 模拟器是一种软件工具&#xff0c;它模拟不同硬件或操作系统环境&#xff0c;使得用户可以在一台 PC 上运行其他平台的应用程序或操作系统。通过 PC 模拟器&#xff0c;用户可以在 Windows 电脑上体验 Android 应用、在 Mac 电脑上运行 Windows …

科普文:详解 JuiceFS 读性能:预读、预取、缓存、FUSE 和对象存储

在高性能计算场景中&#xff0c;往往采用全闪存架构和内核态并行文件系统&#xff0c;以满足性能要求。随着数据规模的增加和分布式系统集群规模的增加&#xff0c;全闪存的高成本和内核客户端的运维复杂性成为主要挑战。 JuiceFS&#xff0c;是一款全用户态的云原生分布式文件…

SQL优化相关

文章目录 SQL优化1. 数据插入2. 主键优化页分裂页合并索引设计原则 3. order by 优化4. group by 优化5. limit优化6. count优化7. update 优化 SQL优化 1. 数据插入 当我们需要插入多条数据时候&#xff0c;建议使用批量插入&#xff0c;因为每次插入数据都会执行一条SQL&am…

【Linux】多线程4——线程同步/条件变量

1.Linux线程同步 1.1.同步概念与线程饥饿问题 先来理解同步的概念 什么是线程同步 在一般情况下&#xff0c;创建一个线程是不能提高程序的执行效率的&#xff0c;所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数&#xff0c;在多个线程同时对同一个内存地…

centos stream 9安装 Kubernetes v1.30 集群

1、版本说明&#xff1a; 系统版本&#xff1a;centos stream 9 Kubernetes版本&#xff1a;最新版(v1.30) docker版本&#xff1a;27.1.1 节点主机名ip主节点k8s-master172.31.0.10节点1k8s-node1172.31.0.11节点2k8s-node2172.31.0.12 2、首先&#xff0c;使用Vagrant和Virt…

前端缓存问题(浏览器缓存和http缓存)- 解决办法

问题描述&#xff1a;前端代码更新&#xff0c;但因浏览器缓存问题&#xff0c;导致页面源代码并未更新 查看页面源代码的方法&#xff1a;鼠标右键&#xff0c;点击查看页面源代码 如图&#xff1a; 解决方法&#xff1a; 注&#xff1a;每执行一步&#xff0c;就检查一下浏览…

string indices must be integers

string indices must be integers 目录 string indices must be integers 【常见模块错误】 【解决方案】 常见原因及解决方法 具体案例分析 总结 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出…

Java1.1标准之重要特性及用法实例(十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

kafka高性能的底层原理分析

目录 1.磁盘顺序写 2.零拷贝 3.数据压缩 4.消息批量处理 5.pageCache 6.稀疏索引 总结 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;它可以处理消费者在网站中的所有动作流数据。那么他是如何做到高性能的呢&#xff0c;本篇文章从宏观上分析一下&#xff…

C++——初识模板

前言 模板是C中的重大板块&#xff0c;是使C真正超越C语言的工具&#xff0c;在C模板没有设计出来之前其实C是没有那么被行业和社会所认可的&#xff0c;本节我们将初步了解C中的模板&#xff08;仅作大致讲解&#xff0c;具体的细枝末节将会再过几节讲解&#xff09;&#xf…

Linuxnat网络配置

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…

一维数组--最长平台

这道题目挺简单的&#xff0c;那你还想这么久&#xff01; 直接看代码&#xff01; #include<cstdio> long long n,a[100002],sum,b[100002],max-99999,j; int main(){scanf("%d",&n);scanf("%d",&a[1]);sum1;for(int i2;i<n;i){j;scan…

【ESP32 IDF 定时器Timer】

目录 TIM定时器介绍硬件定时器和软件定时器硬件定时器基本参数硬件定时器的操作流程初始化硬件定时器设置报警注册回调函数使能和禁用定时器启动和停止定时器硬件定时器驱动代码调试 软件定时器使用软件定时器代码编写 TIM定时器 介绍 定时器是单片机内部集成&#xff0c;可以…

鸿蒙HarmonyOS开发:多种内置弹窗及自定义弹窗的详细使用指南

文章目录 一、消息提示框&#xff08;showToast&#xff09;1、导入模块2、语法3、参数4、示例5、效果 二、对话框&#xff08;showDialog&#xff09;1、导入模块2、语法3、参数4、示例5、效果 三、警告弹窗&#xff08;AlertDialog&#xff09;1、语法2、参数3、AlertDialogP…

STM32的GPIO输入输出方式设置示例

1、GPIO口做基本的输入/输出口使用时&#xff0c;输入有上拉输入、下拉输入、浮空输入&#xff08;既无上拉电阻也无下拉电阻&#xff09;3种输入方式&#xff1b;输出有开漏输出、推挽输出2种输出方式。 2、示例 &#xff08;1&#xff09;示例1&#xff1a;GPIO做输出的设置…