分布式系统全局唯一ID的几种实现方式

现如今可谓是微服务、分布式、IoT(物联网)横行的时代,作为一名开发者始终还是要保持一定的危机意识,特别是在日常的项目开发中,若是有机会接触到一些关于微服务、分布式下的应用场景,应当硬着头皮、排除万难,主动应承下来上去大干一场;这期间不管结果如何,积累下来的经验将会让自己受益匪浅;而本文要介绍的“分布式全局唯一ID”便是一种典型的分布式应用场景!!!
话不多说,咱们直接进入正题~~~

说起这个全局唯一ID,你可能会第一时间想到“数据库的自增主键”、“UUID”、“雪花算法”等等,更有甚者,还能说出一些大厂开源的组件,比如滴滴的IDWorker、美团的Leaf等等,没错,这些确实是可以实现全局唯一ID的方案,你能想到这些点,那涉猎其实还是挺广的。

而对于“全局唯一ID/编号/编码”的应用场景,在现实生活中还是比较多的,比如电商平台中“订单系统”的订单编号,“进销存系统”中的商品编号、订单编号,“支付”过程中订单流水号等等;接下来debug将会总结性的介绍下目前市面上比较流行的“全局唯一ID”的几种实现方式,并针对分布式场景下的几种实现方式进行代码实战。

话不多说,直接进入正题,先贴张思维导图吧,总结性地概括下目前网上比较流行的几种方式img

结合上图几种方式,再概括性的介绍下吧:

1、 数据库的自增主键

简介:这一点相信写过代码的小伙伴都晓得,主要利用主键ID的auo_increment特性,每进来一条数据时数据库自动为其生成当前最大的ID并作为该条记录的主键;

优点:简单、便捷;

缺点:只能限于单机,严重依赖于DB,仅可限于DB相关的业务,可用性还是有点差;

TIPS: 生成的id是连续的吗?https://www.jianshu.com/p/369559f399d0

2、批量预生成ID

简介:DB只存储当前最大的ID值,每次需要ID时,则按照顺序批量生成N个有序的ID列表,并将最大的ID值 + N。

优点:相对于第一种方式性能还是提高了不少;

缺点:仍然得依赖于DB,可用性还是有点差;而且批量生成的ID可能断层(比如服务挂了然后重启就可能跳过部分ID,如果服务有多个,将难以保证其有序性)

TIPS:已废弃 20200708 go服务billno解耦方案

3、 UUID的方式

简介:通用唯一识别码,这个估计众所周知啦,不作过多的介绍了!
优点:简单,直接 UUID.randomUUID().toString()即可搞定;

缺点:比较长、占用空间大;无序且不利于索引,在实际项目中不建议使用;特别是在插入数据库时如果用UUID生成的ID作为主键的话,很可能会引起B+树的不断重平衡;

4、基于时间戳

简介:比如按照规则:yyyyMMdd+ N位随机数 或者 yyyyMMddHHmmss + N位随机数。

优点:可行,而且生成的ID编号前半段有序,有一定的业务意义;

缺点:当并发产生的数据量比较大时,那N位随机数会出现重复的可能(虽然可以通过各种方式去重,比如Redis的Set,但代价还是相当高的,因为得不断的 while判断是否重复…)

**TIPS:**linux下生成随机数 https://blog.51cto.com/13236892/2426623

5、SnowFlake算法

简介:Twitter开源的一种分布式ID生成算法,结果是一个Long型的64位的ID;其核心思想是将64位划分为各个段,其中0号位不用,连续41位表示时间戳,连续10位表示工作机器ID,最后12位则表示毫秒级别的序列号,如下图所示:

img

算法产生的是一个long型 64 比特位的值,第一位未使用。接下来是41位的毫秒单位的时间戳,我们可以计算下:

2^41/1000*60*60*24*365 = 69

也就是这个时间戳可以使用69年不重复,这个对于大部分系统够用了。

很多人这里会搞错概念,以为这个时间戳是相对于一个我们业务中指定的时间(一般是系统上线时间),而不是1970年。这里一定要注意。

10位的数据机器位,所以可以部署在1024个节点。

12位的序列,在毫秒的时间戳内计数。 支持每个节点每毫秒产生4096个ID序号,所以最大可以支持单节点差不多四百万的并发量,这个妥妥的够用了。

优点:可以说是分布式场景下生成全局唯一ID的一种经典算法吧,采用Java生成,对于咱们Java的小伙伴来说可以说是相当接地气的了;

缺点:目前倒没发现有啥缺陷,如果硬要说有,那就是“时钟回播”的问题了。

**TIPS:**时钟回拨问题

  • UidGenerator,时间递增(传统的雪花算法实现都是通过System.currentTimeMillis()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。而UidGenerator的时间类型是AtomicLong,且通过incrementAndGet()方法获取下一次的时间,从而脱离了对服务器时间的依赖,也就不会有时钟回拨的问题)(https://zhuanlan.zhihu.com/p/77737855)
  • leaf 5ms内等待,如果时钟回拨超过5ms,依然会抛出异常。(https://www.jianshu.com/p/bd6b00e5f5ac)
  • sharding jdbc直接拒绝

6、 原子操作类AtomlcXX

简介:JUC(java.util.concurrent)包下经典的原子操作类,可以基于它生成自增、有序且全局唯一的编号

优点:底层采用CAS(Compare And Swap)机制实现,并发场景下可以保证“自增”代码逻辑的安全性;

缺点:依赖于JDK,只适合单机环境

7、Redis的INCRBY操作

简介:熟悉这个命令的应该都知道它是啥意思,不知道的 自己打开redis-cli执行下该命令就可以了!

优点:可行,分布式场景下是适用的;

缺点:基本上没想到有啥缺陷,如果要挑刺的话,那就是依赖于中间件服务,如果Redis挂掉,那基本上该ID生成服务就不可用了

**TIPS:**redis的单机处理是每秒10w左右,如果并发超过10w如何处理?集群式redis如何生成唯一id

8、基于ZooKeeper的节点版本号生成ID

简介:这个大家可能有点陌生,其实就是利用ZooKeeper底层树形节点ZNode(类似于Windows的文件目录数)的有序性,循环不断生成其对应的版本号或者节点本身的数据

优点:可行,分布式场景下是适用的;

缺点:基本上没想到有啥缺陷,跟第七点类似吧,需要保证ZK服务的高可用即可

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

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

相关文章

git如何查看缓存区文件内容_详解Git工作区、暂存区、历史记录区以及git reset、git revert、git checkout等撤销命令的区别...

一、可以将git简单的分为三个区域 1、工作区(working directory) 2、暂缓区(stage index) 3、历史记录区(history) 如图:其中git add files 把当前工作目录中的文件放入暂存区域这其实做了两件事: 1、将本地文件的时间戳、长度&#xff0…

分布式全局唯一ID的实现

分布式全局唯一ID的实现 前言 上周末考完试,这周正好把工作整理整理,然后也把之前的一些素材,整理一番,也当自己再学习一番。 一方面正好最近看到几篇这方面的文章,另一方面也是正好工作上有所涉及,所以决…

mysql多个分类取n条_MySQL获取所有分类和每个分类的前N条记录

MySQL获取所有分类和每个分类的前N条记录。比如有文章表 test(Id,type,tiem),现在要用SQL找出每种类型中时间最新的前N个数据组成的集合,一段不错的代码,留存备用。SELECT a1.* FROM test a1INNER JOIN (SELECT a.type,p.time FROM test aLEF…

Java接口学习(接口的使用、简单工厂、代理模式、接口和抽象类的区别)

前言引入 官方解释:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能&#xff0…

java 矩阵转置_图解利用Java实现数组转置

我们编写Java代码,如下图所示:package com.tina;public class demo {public static void main(String args[]) {int data[] new int[] { 1, 6, 3, 9, 5, 7, 2, 0, 4, 8 };PrintArray(data);}// 输出数组内容public static void PrintArray(int arr[]) {…

java中static、final、static final浅析

final final可以修饰类、属性、方法、局部变量、参数,不能修饰接口!final修饰类:该类不能被继承(解释了为什么不能修饰接口,不过接口里面的属性、方法等是可以用final修饰的);final修饰属性&am…

最短路径 floyd java_java实现Floyd算法求最短路径

关于无向图的最短路径问题:这个程序输出:最短路径矩阵例如:W[0][5]9 代表vo->v5的最短路径为9W:0 1 3 7 4 91 0 2 6 3 83 2 0 4 1 67 6 4 0 3 24 3 1 3 0 59 8 6 2 5 0package com.xh.Floyd;import java.util.ArrayList;public class Floyd_01 {publi…

SpringBoot 使用 log4j2

一、新建工程 选择一些基础依赖 填写工程名称和项目路径 二、工程配置 修改文件编码格式 设置Java Compiler 修改maven配置文件路径 三、pom.xml的web依赖中排除掉logging依赖&#xff0c;并且引入log4j2依赖 <dependency><groupId>org.springframework.…

springBoot 通过使用log4j2

1.排除 Spring-boot-starter 默认的日志配置 将原本的 spring-boot-starter 改为 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><groupId>…

java finalize 何时被调用_finalize()方法什么时候被调用?析构函数(final

finalize()方法也叫收尾方法。一旦垃圾回收器准备好释放对象占用的存储空间&#xff0c;首先会去调用finalize()方法①进行一些必要的清理工作(对垃圾回收器不能处理的特殊情况进行处理)(例子在下边)②也有可能使该对象重新被引用&#xff0c;我习惯叫这种作用为复活。注意&…

编程和java是什么关系_C语言和Java编程有什么区别?

C语言和Java编程有什么区别&#xff1f;Java从根本上说是c之后的一种改进语言&#xff0c;纯面向对象的一种编程语言(当然比起Ruby还是差一点)&#xff0c;有了C语言的基础固然对学习Java有帮助&#xff0c;因为在某种程度上Java和C语言是比较接近的。但是如果没有学习过C语言也…

SpringBoot默认日志logback配置解析

SpringBoot默认日志logback配置解析 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候&#xff0c;是带着下面几个问题来查资料的&#xff0c;你呢 如何引入日志&#xff1f;日志输出格式以及输出方式如何配置&#xff1f;代码中如何使用&#xff1f; 正文…

java lang报错_java.lang.UnsupportedClassVersionError:JDK版本不一致报错

08-15 14:13:29 ERROR doPost(jcm.framework.rmi.RMIServlet:155) -SchedulerService.forceRunJobFlow error.未指定错误&#xff0c;请查看详细信息at jcm.framework.rmi.ClientService.execute(ClientService.java:129)at ...(...)at jcm.flowengine.impl.JobFlowEngine.runJ…

SpringBoot 之Spring Boot Starter依赖包及作用

spring-boot-starter 这是Spring Boot的核心启动器&#xff0c;包含了自动配置、日志和YAML。 spring-boot-starter-amqp 通过spring-rabbit来支持AMQP协议&#xff08;Advanced Message Queuing Protocol. 。 spring-boot-starter-aop 支持面向方面的编程即AOP&#xff0…

java中怎么判断相等_Java中判断相等 (== 与 .equals())

1.Java中有两种判断相等的方法&#xff1a;1.1首先是运算符对于基本类型而言&#xff0c;运算符比较的是值是否相等(本质也是比较的地址&#xff0c;因为常量在常量池中的地址不可改变)int a 3;int b 3;System.out.println(ab);//结果为true对于引用类型而言&#xff0c;运算…

SpringBoot查看和修改依赖的版本

springBoot依赖管理&#xff1a; 1、引入父项目的作用是实现对所有依赖的管理。 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version> &l…

java wait 释放锁_JAVA锁之wait,notify(wait会释放锁,notify仅仅只是通知,不释放锁)...

wait是指在一个已经进入了同步锁的线程内&#xff0c;让自己暂时让出同步锁&#xff0c;以便其他正在等待此锁的线程可以得到同步锁并运行&#xff0c;只有其他线程调用了notify方法(notify并不释放锁&#xff0c;只是告诉调用过wait方法的线程可以去参与获得锁的竞争了&#x…

在IDEA中解决jar包冲突的神操作-必看

在开发过程中&#xff0c;经常会遇到导入jar包后jar包冲突的情况&#xff0c;大家也都知道&#xff0c;解决jar包冲突通常都比较麻烦&#xff0c;要找到多余的依赖&#xff0c;把低版本的依赖去掉。而大家通常能搜到IDEA解决jar包冲突的方法&#xff0c;应该是这样的&#xff1…

java 图片不能正常移动_Java,我的图像不会更新/移动

我对Java的东西是一个新手&#xff0c;但是..在网上阅读了很多内容之后&#xff0c;我一直在努力开发这款游戏并开始使用&#xff0c;我正在使用一些图片。我想通过KeyListener来更新他们的立场以展示运动的过程&#xff0c;我相信。不幸的是&#xff0c;图像仍然在同一个地方&…

MySQL保存或更新 saveOrUpdate

1. 引子 在项目开发过程中&#xff0c;有一些数据在写入时候&#xff0c;若已经存在&#xff0c;则覆盖即可。这样可以防止多次重复写入唯一键冲突报错。下面先给出两个MyBatis配置文件中使用saveOrUpdate的示例 <!-- 单条数据保存 --> <insert id"saveOrUpdat…