文章目录

  • 分布式ID的几种生成方案
    • UUID
    • MySQL主键自增
      • 数据库自增ID改进方案
    • 雪花算法(SnowFlake)
      • 雪花算法的优化
    • Redis自增id
    • Zookeeper有序节点

最近要做区块链项目,要生成很多唯一ID做业务号之类的,所以趁此机会学习学习。

分布式ID的几种生成方案

UUID

之前一直是用的UUID生成唯一ID,好处显而易见,方便快捷,坏处就是:

  1. 数据库里不好做索引,每次生成的ID是无序的,无法保证趋势递增。
  2. UUID的字符串存储,存储空间大,查询效率慢。
  3. ID本事无业务含义,不可读。

很明显,用来做业务号不是UUID的应用场景,使用UUID应该要保障不要求递增,无确实含义的场景,比如说做令牌Token使用。

MySQL主键自增

这个方案就是利用了MySQL的主键自增auto_increment,默认每次ID加1。

这样做的好处:

  1. id是有序的,能够保证自增。
  2. 查询效率高,具有一定的业务可读。

坏处也是显而易见:单点问题,对于单个数据库压力过大,高并发扛不住。

解决方案是按步长自增:

这样能够解决一个单点的问题,缺陷是:

  • 一旦把步长定好后,就无法扩容
  • 虽然相比于单机的方式,数据库压力小了很多,但是还是有一定压力的。

数据库自增ID改进方案

  1. 【用户服务】在注册一个用户时,需要一个用户ID;会请求【生成ID服务(是独立的应用)】的接口。
  2. 【生成ID服务】会去查询数据库,找到user_tag的id,现在的max_id为0,step=1000。(可以加上行锁,防止两个事务请求到相同的结果
  3. 【生成ID服务】把max_id和step返回给【用户服务】;并且把max_id更新为max_id = max_id + step,即更新为1000。
  4. 【用户服务】获得max_id=0,step=1000;这个用户服务可以用ID=【max_id + 1,max_id+step】区间的ID,即为【1,1000】
  5. 【用户服务】会把这个区间保存到jvm中。用户服务】需要用到ID的时候,在区间【1,1000】中依次获取id,可采用AtomicLong中的getAndIncrement方法。
  6. 如果把区间的值用完了,再去请求【生产ID服务】接口,获取到max_id为1000,即可以用【max_id + 1,max_id+step】区间的ID,即为【1001,2000】

这个方案就非常完美的解决了数据库自增的问题,而且可以自行定义max_id的起点,和step步长,非常方便扩容

而且也解决了数据库压力的问题,因为在一段区间内,是在jvm内存中获取的,而不需要每次请求数据库。即使数据库宕机了,系统也不受影响,ID还能维持一段时间。

雪花算法(SnowFlake)

SnowFlake算法生成id的结果是一个64bit大小的整数(也就是long类型,或者说bigInt类型),它的结构如下图:

  • 1bit-不用:
    因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
  • 41bit-时间戳:
    用来记录时间戳,毫秒级。如果只是用来记录整数的时间戳的话,那么41bit实际上可以记载:
    241/(365∗24∗60∗60∗1000ms)2^{41}/(365*24*60*60*1000ms)241/(365∗24∗60∗60∗1000ms),约为69年。
  • 10bit-工作机器id:
    用来记录工作机器id,那么可以部署的机器数目为210=10242^{10}=1024210=1024台机器,包括5位datacenterId(机房id)和5位workerId(机器id)。
  • 12bit-序列号:
    用来记录同毫秒内产生的不同id,也就是一台机器上同一毫秒的并发量是212=40962^{12}=4096212=4096次。

SnowFlake可以保证:

  1. 所有生成的id按时间趋势递增
  2. 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)

这里分析一下生成ID的函数。

 //下一个ID生成算法//使用了synchronized生成ID,做一个阻塞,防止同一毫秒内生成相同的12位序列号public synchronized long nextId() {long timestamp = timeGen();//获取到当前的时间戳//获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常if (timestamp < lastTimestamp) {System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",lastTimestamp - timestamp));}//获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;if (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}} else {sequence = 0;}//将上次时间戳值刷新lastTimestamp = timestamp;/*** 返回结果:* (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数* (datacenterId << datacenterIdShift) 表示将数据id左移相应位数* (workerId << workerIdShift) 表示将工作id左移相应位数* | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。* 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id*/return ((timestamp - twepoch) << timestampLeftShift) |(datacenterId << datacenterIdShift) |(workerId << workerIdShift) |sequence;}

优点:

  • 此方案每秒能够产生409.6万个ID,性能快
  • 时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序递增
  • 灵活度高,可以根据业务需求,调整bit位的划分,满足不同的需求

缺点:

  • 依赖机器的时钟,如果服务器时钟回拨,会导致重复ID生成。
    在分布式场景中,服务器时钟回拨会经常遇到(时间校准,以及其他因素,可能导致服务器时间回退),一般存在10ms之间的回拨;小伙伴们就说这点10ms,很短可以不考虑吧。但此算法就是建立在毫秒级别的生成方案,一旦回拨,就很有可能存在重复ID。

雪花算法的优化


  • synchronized关键字:

此锁的目的是为了保证在多线程的情况下,只有一个线程进入方法体生成ID,保证并发情况下生成ID的唯一性,如果在竞争激烈情况下,自旋锁+ CAS原子变量的方式或许是更为合理的选择,可以达到优化部分性能的目的。


  • 时钟回拨问题:

UidGenerator是百度开源的Java语言实现,基于Snowflake算法的唯一ID生成器。另外,它通过消费未来时间克服了雪花算法的并发限制。UidGenerator提前生成ID并缓存在RingBuffer中。

RingBuffer,如下图所示,它本质上是一个数组,数组中每个项被称为slot。UidGenerator设计了两个RingBuffer,一个保存唯一ID,一个保存flag。RingBuffer的尺寸是2^n,n必须是正整数:

  • RingBuffer of Flag:
    保存flag这个RingBuffer的每个slot的值都是0或者1,0是CAN_PUT_FLAG的标志位,1是CAN_TAKE_FLAG的标识位。也就是可以放置UID或者拿取UID的标识。

  • RingBuffer of UID:
    保存唯一ID的RingBuffer有两个指针,Tail指针和Cursor指针。
    Tail指针表示最后一个生成的唯一ID。如果这个指针追上了Cursor指针,意味着RingBuffer已经满了。这时候,不允许再继续生成ID了。
    Cursor指针表示最后一个已经给消费的唯一ID。如果Cursor指针追上了Tail指针,意味着RingBuffer已经空了。这时候,不允许再继续获取ID了。


初始化阶段

  1. 根据boostPower的值确定RingBuffer的size。bufferSize=2132^{13}213, 扩容后bufferSize =2132^{13}213<<boostPower(位移操作)。
  2. 构造RingBuffer,默认paddingFactor为50。这个值的意思是当RingBuffer中剩余可用ID数量少于50%的时候,就会触发一个异步线程往RingBuffer中填充新的唯一ID。
  3. 初始化PUT和TAKE的拒绝策略,也就是满了或者空了之后应该怎么做。
  4. 初始化填满RingBuffer中所有slot(填满所有ID)。

百度UidGenerator的优势:

  • 不依赖系统时间:
    传统的雪花算法实现都是通过System.currentTimeMillis()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。而UidGenerator的时间类型是AtomicLong,且通过incrementAndGet()方法获取下一次的时间,从而脱离了对服务器时间的依赖,也就不会有时钟回拨的问题(这种做法也有一个小问题,即分布式ID中的时间信息可能并不是这个ID真正产生的时间点,例如:获取的某分布式ID的值为3200169789968523265,它的反解析结果为{“timestamp”:“2019-05-02 23:26:39”,“workerId”:“21”,“sequence”:“1”},但是这个ID可能并不是在"2019-05-02 23:26:39"这个时间产生的)。
  • 使用缓存

Redis自增id

利用redis的incr原子性操作自增,一般算法为:年份 + 当天距当年第多少天 + 天数 + 小时 + redis自增。

优点:

  • 有序递增,可读性强。
  • 性能还可以。

缺点:

  • 占用带宽,每次要向redis进行请求,并发强依赖了Redis
  • ID安全性的问题,如:Redis方案中,用户是可以预测下一个ID号是多少,因为算法是递增的。(当然自增的ID都存在这样的问题
    比如,竞争对手第一天中午12点下个订单,就可以看到平台的订单ID是多少,第二天中午12点再下一单,又平台订单ID到多少。这样就可以猜到平台1天能产生多少订单了。

Zookeeper有序节点

通过创建ZK的顺序模式的节点,可以生成全局唯一的ID。

Java架构直通车——分布式唯一 ID生成方案相关推荐

  1. 一线大厂的分布式唯一ID生成方案是什么样的?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户 ...

  2. 一起学习下一线大厂的分布式唯一ID生成方案!

    来源 | http://www.toutiao.com/i6682672464708764174 一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法 ...

  3. mysql 分布式 生成序号_分布式唯一ID生成方案

    唯一ID在业务系统中经常用到,例如数据库的唯一主键,那么唯一ID如何生成,我们这里介绍一些常见的实现方案. 字符串ID 如果采用字符串id,那么很简单,直接使用jdk自带的UUID,原始生成的是带中划 ...

  4. 常用的分布式唯一ID生成方案

    全局唯一ID使用场景 分布式系统设计时,数据分片场景下,通常需要一个全局唯一id: 在消息系统中需要消息唯一ID标识来防止消息重复: 多系统打通需要一个全局唯一标识 (如集团各业务线面对不同用户,需要 ...

  5. 分布式唯一ID生成方案

    ​ 在应用程序中,经常需要全局唯一的ID作为数据库主键.如何生成全局唯一ID? ​ 首先,需要确定全局唯一ID是整型还是字符串?如果是字符串,那么现有的UUID就完全满足需求,不需要额外的工作.缺点是 ...

  6. Java分布式唯一ID生成方案——比UUID效率更高的生成id工具类

    package com.xinyartech.erp.core.util;import java.lang.management.ManagementFactory; import java.net. ...

  7. Java秒杀系统实战系列~分布式唯一ID生成订单编号

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第七篇,在本博文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们 ...

  8. java 唯一编号_Java秒杀系统实战系列~分布式唯一ID生成订单编号

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第七篇,在本博文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们 ...

  9. 分布式系统唯一ID生成方案浅析

    有情怀,有干货,微信搜索[荒古传说]关注这个不一样的程序员. 分布式系统唯一ID生成方案浅析 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.业务ID需要满足的要求如下 全局唯一性:不能出 ...

最新文章

  1. Vue2.0使用vue-cli脚手架搭建
  2. 遗传算法 python包_遗传算法 (Genetic Algorithm)
  3. CentOS设置服务开机启动的方法
  4. C++11 (多线程)并发编程总结
  5. Accent-Insensitive, Accent Sensitive, a ã, e é 模糊查询
  6. Winform中实现对照片添加文字和图片水印(附代码下载)
  7. 某大型企业私有云建设思路解析
  8. 足不出户,游遍七大洲,不可错过的14部地理纪录片!
  9. jQuery中的几个案例:隔行变色、复选框全选和全不选
  10. HEVC/H265 文档获得
  11. 背景色渐变html代码,求html文字背景色渐变的代码
  12. mysql 去重命令_MySQL 命令操作
  13. 如何实现Miracast多个设备同时连接投屏
  14. javaweb零食商城系统设计与实现(ssm项目)(含论文和源码)
  15. 《你是三月的小桨》光剑
  16. rgb sw 线主板接口在哪_有颜值也有实力!利民TL-C12S幻彩RGB电脑散热风扇评测
  17. java qlv转mp4 代码_怎么将qlv格式转换成mp4?教你快速转换视频格式的技巧
  18. vue网页打印后事件失效
  19. Ubuntu如何通过简单的几步操作来配置系统的软件源、找不到软件和更新怎么办?(带动态图文介绍)
  20. echart旭日图_基于Echarts4.0实现旭日图

热门文章

  1. Django DRF 视图集
  2. 记一次面试及总结:上海春翔网络科技有限公司
  3. springboot 使用itextpdf 框架实现多个图片合成一个pdf文件
  4. Linux应用层——IO(输入输出)编程
  5. intel 服务器主板芯片,台积电3nm制程获Intel订单,不打造手机芯片太可惜了
  6. linux代码生成时间戳,linux生成连续时间戳
  7. 怎么做新闻营销?新闻营销如何“借势”?
  8. c++ 11 recursive_mutex 递归锁
  9. matlab2006b plp,Matlab R2006b安装教程
  10. 第一章:基于 SpringBoot 快速搭建QQ机器人,并监听群事件