分布式系统全局唯一ID的几种实现方式
现如今可谓是微服务、分布式、IoT(物联网)横行的时代,作为一名开发者始终还是要保持一定的危机意识,特别是在日常的项目开发中,若是有机会接触到一些关于微服务、分布式下的应用场景,应当硬着头皮、排除万难,主动应承下来上去大干一场;这期间不管结果如何,积累下来的经验将会让自己受益匪浅;而本文要介绍的“分布式全局唯一ID”便是一种典型的分布式应用场景!!!
话不多说,咱们直接进入正题~~~
说起这个全局唯一ID,你可能会第一时间想到“数据库的自增主键”、“UUID”、“雪花算法”等等,更有甚者,还能说出一些大厂开源的组件,比如滴滴的IDWorker、美团的Leaf等等,没错,这些确实是可以实现全局唯一ID的方案,你能想到这些点,那涉猎其实还是挺广的。
而对于“全局唯一ID/编号/编码”的应用场景,在现实生活中还是比较多的,比如电商平台中“订单系统”的订单编号,“进销存系统”中的商品编号、订单编号,“支付”过程中订单流水号等等;接下来debug将会总结性的介绍下目前市面上比较流行的“全局唯一ID”的几种实现方式,并针对分布式场景下的几种实现方式进行代码实战。
话不多说,直接进入正题,先贴张思维导图吧,总结性地概括下目前网上比较流行的几种方式
结合上图几种方式,再概括性的介绍下吧:
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位则表示毫秒级别的序列号,如下图所示:
算法产生的是一个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服务的高可用即可
相关文章
- .NET 实现并行的几种方式(三)
- 【Spring AOP】Spring AOP之如何通过注解的方式实现各种通知类型的AOP操作进阶篇(3)
- 【Spring Boot】Spring Boot之整合Sharding-JDBC(java config方式)实现分库分表(水平拆分)
- C#实现文件下载的几种方式
- java核心知识点学习----多线程间的数据共享的几种实现方式比较
- java核心知识点学习----多线程间的数据共享的几种实现方式比较
- SQLServer · 最佳实践 · 数据库实现大容量插入的几种方式
- 【STM32H7教程】第60章 STM32H7的DAC应用之定时器触发实现DMA方式双通道波形
- 亿级Web系统搭建 Web负载均衡的几种实现方式(阿里)
- 简单的一个盒子移动到另一个盒子,你用什么方式实现动画效果
- JavaScript 实现网页截屏五种方法
- SQLServer · 最佳实践 · 数据库实现大容量插入的几种方式
- js实现页面跳转的几种方式
- 程序实现对数据排序并按出现次数进行排序 程序实现对数据排序并按出现次数进行排序(注:用面向对象的方式实现,用for循环进行排序,别用comparable接口实现){1,4,2,1,3,2,1,4}作为
- Atitit.hybrid混合型应用 浏览器插件,控件的实现方式 浏览器运行本地程序的解决方案大的总结---提升用户体验and开发效率..
- Atitit.javascript 实现类的方式原理大总结
- 二进制数据的贝叶斯非参数聚类算法(Matlab代码实现)
- 用Python实现九九乘法表的几种方式,最简单只需一行代码
- Python实现九九乘法表的几种方式,入门必备案例~超级简单~
- 教你3种Kafka的指定副本作为Leader的实现方式
- 利用spring实现服务启动就自动执行某些操作的2种方式
- MongoDB Wiredtiger存储引擎实现原理——Copy on write的方式管理修改操作,Btree cache
- 数据结构与算法_15 _ 二分查找(上):如何用最省内存的方式实现快速查找功能
- rabbitmq 延时队列 插件方式实现 每条消息都延时自己时间
- 使用MSIL采用Emit方式实现C#的代码生成与注入
- 【Tensorflow+keras】Keras API三种搭建神经网络的方式及以mnist举例实现
- Shell脚本中实现循环的方式,你会几种?
- Java 实现标准输入的几种方式