详解分布式ID实践

news/2025/2/22 11:26:25

引言

分布式ID,所谓的分布式ID,就是针对整个系统而言,任何时刻获取一个ID,无论系统处于何种情况,该值不会与之前产生的值重复,之后获取分布式ID时,也不会再获取到与其相同的值,它是一个绝对意义上的全局唯一值。

那么,究竟什么情况下需要用到分布式ID呢?

最经典的场景是分库分表,还是以用户数据来举例子,之前只有一张用户表,所以设置表ID自增后,每新增一条数据都会自增ID值,从而确保了ID永远不会重复。
此刻用户表被分成了十张,如果再依靠数据库本身的自增机制来分配ID,显然会导致ID重复,这时分布式ID就派上了用场。除开分库分表外,通常还会用到分布式ID的场景有:

  • 链路ID分布式链路中,需要通过全局唯一的traceId来串联所有日志;
  • 请求ID:幂等性处理时,需要通过唯一的ID来判断是否为重复请求;
  • 消息标识:MQ需要基于唯一的msgID来区分数据,确保数据不重复或丢失;
  • 短链码:生成短链接时,需要获取一个全局唯一的值作为Code避免重复;

下面将阐述怎么在项目中实践,为我们的对象生成一个分布式ID

UUID生成

String uuid = UUID.randomUUID().toString();
System.out.println(uuid);

/*
* 输出结果:
*   b2c2ec5d-efb9-44c7-b2c8-9cef367c8b3f
* */

通过JDK提供的UUID工具类,一行代码就能生成一个UUID,并且得到的UUID不会重复,怎么保障的呢?UUID的底层,会基于硬件地址(MAC地址)、时间戳和随机因子来生成ID。
世界上没有两片完全相同的叶子,者如这句话一般,世界上也没有两台完全相同的机器,这时硬件地址自然不同,再加上正常情况下不可逆转的时间戳,以及一定范围的随机数,就能确保产生的UUID,其全球唯一性。

雪花算法

雪花算法生成的分布式ID,在Java中会使用Long类型来承载,Long类型占位8bytes,也就正好对应上述这张图的64个比特位,这64bit会被分为四部分:

  • 符号位(1bit):永远为零,表示生成的分布式ID为正数。
  • 时间戳位(2~42bit):会将当前系统的时间戳插入到这段位置。
  • 工作进程位(43~53bit):在集群环境下,每个进程唯一的工作ID
  • 序列号位(54~64bit):该序列是用来在同一个毫秒内生成不同的序列号。
/*
* 雪花算法实现类
* */
public class Snowflake implements Serializable {
    private static final long serialVersionUID = 1L;
    // 雪花算法的起始时间纪元
    public static long DEFAULT_TWEPOCH = 1288834974657L;
    public static long DEFAULT_TIME_OFFSET = 2000L;
    
    // 机器标识所占的位数
    private static final long WORKER_ID_BITS = 5L;
    // 数据中心标识所占的位数
    private static final long DATA_CENTER_ID_BITS = 5L;
    // 毫秒内的自增位数
    private static final long SEQUENCE_BITS = 12L;
    // 机器ID最大值
    private static final long MAX_WORKER_ID = 31L;
    // 数据中心ID最大值
    private static final long MAX_DATA_CENTER_ID = 31L;
    // 机器ID左移12位
    private static final long WORKER_ID_SHIFT = 12L;
    // 数据中心左移17位
    private static final long DATA_CENTER_ID_SHIFT = 17L;
    // 毫秒时间戳左移22位
    private static final long TIMESTAMP_LEFT_SHIFT = 22L;
    private static final long SEQUENCE_MASK = 4095L;
    // 雪花ID相关组成部分的定义
    private final long twepoch;
    private final long workerId;
    private final long dataCenterId;
    private final boolean useSystemClock;
    private final long timeOffset;
    private final long randomSequenceLimit;
    private long sequence;
    // 最近一次生产ID的时间戳
    private long lastTimestamp;

    public Snowflake() {
        this(IdUtil.getWorkerId(IdUtil.getDataCenterId(31L), 31L));
    }

    public Snowflake(long workerId) {
        this(workerId, IdUtil.getDataCenterId(31L));
    }

    public Snowflake(long workerId, long dataCenterId) {
        this(workerId, dataCenterId, false);
    }

    public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) {
        this((Date)null, workerId, dataCenterId, isUseSystemClock);
    }

    public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
        this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET);
    }

    public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {
        this(epochDate, workerId, dataCenterId, isUseSystemClock, timeOffset, 0L);
    }

    public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset, long randomSequenceLimit) {
        this.sequence = 0L;
        this.lastTimestamp = -1L;
        this.twepoch = null != epochDate ? epochDate.getTime() : DEFAULT_TWEPOCH;
        this.workerId = Assert.checkBetween(workerId, 0L, 31L);
        this.dataCenterId = Assert.checkBetween(dataCenterId, 0L, 31L);
        this.useSystemClock = isUseSystemClock;
        this.timeOffset = timeOffset;
        this.randomSequenceLimit = Assert.checkBetween(randomSequenceLimit, 0L, 4095L);
    }

    public long getWorkerId(long id) {
        return id >> 12 & 31L;
    }

    public long getDataCenterId(long id) {
        return id >> 17 & 31L;
    }

    public long getGenerateDateTime(long id) {
        return (id >> 22 & 2199023255551L) + this.twepoch;
    }

    public synchronized long nextId() {
        long timestamp = this.genTime();
        // 解决时钟回拨问题
        if (timestamp < this.lastTimestamp) {
            if (this.lastTimestamp - timestamp >= this.timeOffset) {
                throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", new Object[]{this.lastTimestamp - timestamp}));
            }

            timestamp = this.lastTimestamp;
        }

        if (timestamp == this.lastTimestamp) {
            long sequence = this.sequence + 1L & 4095L;
            if (sequence == 0L) {
                timestamp = this.tilNextMillis(this.lastTimestamp);
            }

            this.sequence = sequence;
        } else if (this.randomSequenceLimit > 1L) {
            this.sequence = RandomUtil.randomLong(this.randomSequenceLimit);
        } else {
            this.sequence = 0L;
        }

        this.lastTimestamp = timestamp;
        return timestamp - this.twepoch << 22 | this.dataCenterId << 17 | this.workerId << 12 | this.sequence;
    }

    public String nextIdStr() {
        return Long.toString(this.nextId());
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp;
        for(timestamp = this.genTime(); timestamp == lastTimestamp; timestamp = this.genTime()) {
        }

        if (timestamp < lastTimestamp) {
            throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", new Object[]{lastTimestamp - timestamp}));
        } else {
            return timestamp;
        }
    }

    private long genTime() {
        return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();
    }
}

上面代码可以直接引用来作为我们的雪花算法工具类

由于原始版雪花算法存在的问题,出现了许多改良版算法,但其核心还是前面聊到的雪花算法,比如百度的Uid-Generator算法、美团的Leaf算法-雪花模式、阿里的Seata框架里的雪花算法实现等等,感兴趣的小伙伴可以自行翻阅源码。

以上就是最常用的实践方式欢迎大家补充


http://www.niftyadmin.cn/n/5862182.html

相关文章

基于Flask的租房信息可视化系统的设计与实现

【Flask】基于Flask的租房信息可视化系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 随着互联网的快速发展&#xff0c;租房市场日益繁荣&#xff0c;信息量急剧增加&#xff…

物联网与大数据:揭秘万物互联的新纪元

物联网与大数据&#xff1a;揭秘万物互联的新纪元 在当今高速发展的科技时代&#xff0c;物联网&#xff08;IoT&#xff09;和大数据无疑是推动各行各业转型和创新的重要力量。通过将日常生活中的各种设备连接至互联网&#xff0c;并利用大数据技术进行实时分析&#xff0c;我…

RD-搭建测试环境

测试团队职责 环境验证&#xff1a;确保开发部署的测试环境可访问&#xff0c;页面/接口无阻塞问题&#xff1b; 配置检查**&#xff1a;核对数据库连接、接口域名、HT证书等关键配置&#xff1b; 数据准备**&#xff1a;导入基线数据&#xff0c;隔离测试与生产数据&#xff1…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_test_full_name

ngx_test_full_name 声明在 src\core\ngx_file.c static ngx_int_t ngx_test_full_name(ngx_str_t *name); 定义在 src\core\ngx_file.c static ngx_int_t ngx_test_full_name(ngx_str_t *name) { #if (NGX_WIN32)u_char c0, c1;c0 name->data[0];if (name->len <…

探索关键领域的AI工具:机器学习、深度学习、计算机视觉与自然语言处理

引言 在人工智能(AI)迅猛发展的今天&#xff0c;机器学习(ML)、深度学习(DL)、计算机视觉(CV)和自然语言处理(NLP)已经成为解决复杂问题的关键技术。无论是自动驾驶车辆的视觉识别&#xff0c;还是智能助手的对话理解&#xff0c;这些技术都在改变着世界。本文将介绍在各个领域…

百万架构师第三十七课:RabbitMq:高可用集群搭建步骤|JavaGuide

安装环境 Centos-7 三台虚拟机 192.168.8.150&#xff08;磁盘节点&#xff09; 192.168.8.45 &#xff08;内存节点&#xff09; 192.168.8.40 &#xff08;内存节点&#xff09;一、安装Erlang 1、erlang 下载地址&#xff1a; http://www.rabbitmq.com/releases/erlang…

Unity摄像机与灯光相关知识

一、Inspector窗口 Inspector窗口可以查看和编辑对象的属性以及设置 其中包含各种组件&#xff0c;例如用Cube对象来举例 1.Sphere(Mesh)组件&#xff1a; 用来决定对象的网格属性&#xff0c;例如球体网格为Sphere、立方体网格为Cube 2.Mesh Renderer组件&#xff1a; 用来设置…

【弹性计算】虚拟化技术

虚拟化技术 1.虚拟化技术的发展历程1.1 Xen 虚拟化架构&#xff08;2009 ~ 2015 年&#xff09;1.2 KVM 虚拟化架构&#xff08;2015 ~ 2018 年&#xff09;1.3 软硬件结合的虚拟化架构&#xff08;2018 年至今&#xff09; 2.KVM 虚拟化技术3.热迁移技术4.热升级技术4.1 内核热…