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

2023-11-05

最近要做区块链项目,要生成很多唯一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实际上可以记载:
    2 41 / ( 365 ∗ 24 ∗ 60 ∗ 60 ∗ 1000 m s ) 2^{41}/(365*24*60*60*1000ms) 241/(3652460601000ms),约为69年。
  • 10bit-工作机器id:
    用来记录工作机器id,那么可以部署的机器数目为 2 10 = 1024 2^{10}=1024 210=1024台机器,包括5位datacenterId(机房id)和5位workerId(机器id)。
  • 12bit-序列号:
    用来记录同毫秒内产生的不同id,也就是一台机器上同一毫秒的并发量是 2 12 = 4096 2^{12}=4096 212=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= 2 13 2^{13} 213, 扩容后bufferSize = 2 13 2^{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。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java架构直通车——分布式唯一 ID生成方案 的相关文章

  • java参数传递(局部变量)

    基本数据类型 传递的是值的副本 修改副本不会对原值造成影响 也就是值传递 Byte Short Integer Long Float Double Boolean Character 引用数据类型 除开基本数据类型的所有数据类型 传递的是对
  • centos8 安装notepadqq

    yum install epel release yum install snapd systemctl enable now snapd socket snap install notepad plus plus yum install

随机推荐

  • 【附源码】计算机毕业设计SSM汽车维修服务系统

    项目运行 环境配置 Jdk1 8 Tomcat7 0 Mysql HBuilderX Webstorm也行 Eclispe IntelliJ IDEA Eclispe MyEclispe Sts都支持 项目技术 SSM mybatis Ma
  • FTP使用教程

    FTP使用教程 目录 一 FTP简介 二 FTP搭建 三 FTP使用 一 FTP简介 FTP中文为文件传输协议 简称为文传协议 它也是一个应用程序 不同的操作系统有不同的FTP应用程序 这些应用程序都遵守同一种协议以传输文件 FTP主要的功
  • KD树

    KD树 1 概述 KD树是一种查询索引结构 广泛应用于数据库索引中 从概念的角度讲 它是一种高纬数据的快速查询结构 本文首先介绍1维数据的索引查询 然后介绍2维KD树的创建和查询 2 1维数据的查询 假设在数据库的表格T中存储了学生的语文成
  • 数据挖掘概述

    1 数据挖掘定义 数据挖掘是从大量的 不完全的 有噪声的 模糊的 随机的应用数据中 提取出潜在且有用的信息的过程 2 数据分析的过程 1 确定知识发现的过程 2 数据采集 数据质量决定挖掘的上限 而算法仅仅是逼近这个上限 3 数据搜索 将数
  • 怎么做好中层管理

    随着工作经验和年限不断增加 经过努力相信很多IT技术人员和我一样从一个开发角色转变为技术管理或者部门管理角色 一方面需要做技术解决方案架构开发上工作 带领团队攻关技术难题 同时也会面临一些团队管理和团队业绩增长需要不断去提高和突破 虽然不必
  • 基于MATLAB的遗传算法优化混合流水车间调度问题

    基于MATLAB的遗传算法优化混合流水车间调度问题 混合流水车间调度问题是在车间生产中常见的一个优化问题 旨在通过合理安排作业顺序和机器分配 最大限度地提高生产效率和降低生产成本 本文将介绍如何使用MATLAB编写遗传算法来解决混合流水车间
  • Ubuntu ssh root 登录不了解决办法

    Ubuntu在安装的时候 设置了sudo的密码 但是这并不是root用户的密码 导致即使修改了 etc ssh sshd config 文件 运行root用户登录 还是会出现Permission denied 真是坑啊 差点没把我气到吐血
  • 发送html邮件时遇到的奇怪的乱码问题

    解析文件字符流 作为内容发送邮件到客户端 解析的代码如下 BufferedReader reader new BufferedReader new FileReader file 发现windows系统下发送的邮件时是正常的 在linux下
  • NETPLIER : 一款基于概率的网络协议逆向工具(一)理论

    本文系原创 转载请说明出处 信安科研人 关注微信公众号 信安科研人 获取更多网络安全学术技术资讯 今日介绍一篇发表在2021 NDSS会议上的一项有关协议逆向的工作 文章目录 1 网络协议逆向工程简介 1 1 协议逆向工程的主流技术 案例
  • 【计算机网络】第一章知识点汇总

    第一章学习重点 2 5 因特网的组成 通信方式与交换方式 4 4 时延 计算 4 5 时延带宽积 计算 5 2 协议的划分与层次 5 3 五层协议的体系结构 OSI与TCP IP的比较 1 计算机网络在信息时代的作用 三网融合中的 三网 指
  • Hadoop-HBase 单机部署

    一 系统版本 Linux系统 wdOS 1 0 x86 64 iso 关于wdOS说明 1 安装简单 快速 去掉了安装过程中不必要的烦锁操作和不必要的选择 2 可选安装集成web环境 如lamp lnmp lnamp 并可相互自由切换使用
  • 设计模式之适配器模式(Adapter)

    1 定义 适配器模式 Adapter 指的是将一个类的接口转换成另一个可以兼容的接口 比如我们日常生活中的转换头 古早时期使用的电池万能充 就相当于程序中使用的适配器模式 2 适配器模式的种类 2 1 类适配器模式 类适配器模式通过多重继承
  • 大数据项目实战——基于某招聘网站进行数据采集及数据分析(三)

    大数据项目实战 第三章 数据采集 文章目录 大数据项目实战 学习目标 一 分析与准备 1 分析网页结构 2 数据采集环境准备 二 采集网页数据 1 创建响应结果 JavaBean 类 2 封装 HTTP 请求的工具类 1 定义三个全局变量
  • 14张自动化测试框架结构图(建议收藏)

    1 接口自动化测试框架设计图 接口自动化测试框架设计图 2 接口自动化执行设计图 接口自动化执行设计图 3 API自动化平台框架设计图 API自动化平台框架设计图 4 UI自动化测试框架设计图 UI自动化测试框架设计图 5 接口 UI自动化
  • 【CUDA基础练习】向量内积计算的若干种方法

    先从一个简单 直观的方法来了解如何用CUDA计算向量内积 向量内积既然是将两个向量对应元素相乘的结果再求和 我们先考虑将对应元素相乘并行化 再来考虑相加 方法一 include
  • 十八、kubernetes中容器重启策略

    1 概述 在上一篇文章中 一旦容器探测出现了问题 kubernetes就会对容器所在的Pod进行重启 其实这是由pod的重启策略决定的 pod的重启策略有 3 种 分别如下 Always 容器失效时 自动重启该容器 这也是默认值 OnFai
  • oracle行转列(PIVOT),列转行(UNPIVOT)

    1 行转列 PIVOT 现有 学生 分数表 STUDENT SCORE 如下 想看到每个学生语数外的整体分数情况 这时候可以应用行转列 PIVOT SELECT FROM STUDENT SCORE PIVOT SUM SCORE FOR
  • C++应用到C# ref , out

    include stdafx h include iostream h int factor int int int void main int number squard cubed error cout lt lt Enter the
  • 泰勒展开式求sin(x)

    include
  • Java架构直通车——分布式唯一 ID生成方案

    文章目录 分布式ID的几种生成方案 UUID MySQL主键自增 数据库自增ID改进方案 雪花算法 SnowFlake 雪花算法的优化 Redis自增id Zookeeper有序节点 最近要做区块链项目 要生成很多唯一ID做业务号之类的 所