SpringBoot天生唯一ID的方式

[复制链接]
发表于 2025-10-21 13:58:03 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
1.为什么要天生唯一ID?

数据唯一性:每个记载都必要有一个独一无二的标识符来确保数据的唯一性。这可以克制重复的数据行,并有助于精确地查询、更新或删除特定的记载。
数据完备性:通过利用唯一ID,可以包管数据库中的数据完备性。比方,在关系数据库中,外键通常引用主表的唯一ID,以创建和维护表之间的关联。
高效查询:唯一ID通常是索引的一部分,这意味着利用它们举行查询可以非常快速和高效。这对于进步应用步伐性能特别紧张,尤其是在处置惩罚大量数据时。
简化逻辑处置惩罚:拥有一个唯一的标识符可以简化许多业务逻辑的处置惩罚。比方,当必要引用或共享特定命据项时,唯一ID提供了一个简朴而直接的方法。
分布式体系支持:在分布式体系或微服务架构中,唯一ID(特别是全局唯一ID)是必不可少的。它们允许差别的服务可以或许独立天生不辩论的ID,从而简化了跨服务的数据整合和通讯标题。
易于扩展和维护:随着体系的发展和需求的变革,唯一ID使得添加新功能大概修改现有功能变得更加轻易。比方,重构数据库布局时,唯一ID可以资助更平滑地迁移数据。
2.天生唯一ID的根本要求

唯一性:这是最根本的要求,即天生的ID在整个体系中必须是独一无二的。特别是在分布式体系中,必要确保差别节点天生的ID也不会发生辩论。
全局唯一性(如果实用):在某些场景下,如分布式体系或微服务架构中,大概必要ID不但在同一数据库或体系内唯一,而且在全部相干的体系和数据库中也保持唯一。
不可推测性:为了安全思量,尤其是在涉及用户敏感信息或紧张业务逻辑时,ID应该是难以推测的。这可以防止恶意推测或其他安全标题。
序次性(如果必要):有些应用大概必要ID有肯定的序次性,比方按时间序次分列,以便于排序、分页等利用。但必要留意的是,严格的序次性大概会对体系的扩展性和性能造成影响。
高效性:天生ID的过程应该尽大概快速且斲丧资源少,以克制成为体系性能瓶颈。
长度适中:ID的长度应该适中,既能包管充足的空间来确保唯一性,又不至于过长导致存储和传输服从低沉。
兼容性:天生的ID应与现有的体系和协议兼容,不会由于格式或编码标题而引起错误。
可扩展性:随着体系的发展和规模的增长,天生ID的方法应能方便地举行扩展,以顺应更高的需求。
3.常见天生方法


  • UUID(通用唯一辨认码)
基于时间的UUID(Version 1):结适时间戳和MAC地点天生。
基于随机数的UUID(Version 4):利用随机数天生。
基于定名空间的UUID(Version 3 和 5):基于定名空间和名称的哈希值天生。
长处:
全局唯一性:UUID的筹划包管了在举世范围内的唯一性,实用于分布式体系。
无需中央和谐:无需依靠数据库或其他中央服务天生ID,镌汰了体系复杂性。
天生速率快:基于随机数的UUID天生速率非常快,实用于高并发场景。
缺点:
长度较长:UUID长度为128位,通常表现为36个字符(包罗连字符),这在存储和传输时占用较多空间。比方,在数据库中存储UUID会比存储整数范例占用更多的空间。
无序性:UUID通常是随机天生的,缺乏时间上的序次性。这在数据库索引中大概导致性能标题,由于插入利用大概必要在B树索引中频仍分裂节点。
可读性差:UUID对人类不友好,难以影象和辨认,倒霉于在用户界面或日记中直接利用。
缺乏业务相干性:UUID不包罗任何业务信息,如时间戳或地区信息,难以用于业务分析和追踪。

  • 实用场景


  • 分布式体系:在多节点、多数据中央的情况中,UUID是天生唯一标识符的理想选择。
  • 无需排序的场景:如果不必要按时间或其他序次对ID举行排序,UUID是一个不错的选择。
  • 高并发情况:UUID的天生速率非常快,实用于必要快速天生唯一标识符的高并发场景。


  • 数据库自增ID
数据库自增ID是一种常见的天生唯一标识符的方法,通过在数据库表中设置自增字段(如AUTO_INCREMENT),每次插入新记载时,数据库会主动为该字段天生一个唯一的、递增的整数。
长处:
简朴易用:实现简朴,只需在数据库表中设置自增字段,无需额外的代码或设置。
有序性:自增ID是有序的,有利于数据库索引的性能,特别是在利用B树索引时。
节流存储空间:整数范例的ID通常比UUID占用更少的存储空间。
可读性较好:整数ID对人类相对友好,易于辨认和影象。
缺点:
单点瓶颈:在分布式数据库情况中,自增ID难以包管全局唯一性,通常必要依靠单个数据库实例来天生ID,这会成为体系的单点瓶颈,影响性能和可扩展性。
难以程度扩展:如果体系必要程度扩展到多个数据库实例,自增ID的天生会成为标题,由于每个实例都会天生自己的ID序列,导致ID辩论。
依靠数据库:天生ID依靠于数据库,这在数据库故障或高负载时大概成为标题。
缺乏业务相干性:自增ID不包罗任何业务信息,如时间戳或地区信息,难以用于业务分析和追踪。

  • 实用场景


  • 单体应用:在单体应用中,数据库自增ID是一个简朴且有效的方法。
  • 无需分布式唯一性的场景:如果体系不必要在多个数据库实例或多个数据中央中保持全局唯一性,自增ID是一个不错的选择。
  • 对ID有序性有要求的场景:如果必要按时间或其他序次对ID举行排序,自增ID的有序性是一个上风。
4.雪花算法的实现过程如下

获取当前时间戳,精确到毫秒级别。
根据给定的数据中央ID和呆板ID,天生一个10位的二进制数。
将时间戳左移22位,将数据中央ID左移17位,将呆板ID左移12位,然后利用位或利用符将它们组合成一个64位的二进制数。
如果在同一毫秒内天生了多个ID,利用序列号来区分它们,序列号从0开始递增,最多可以天生4096个序列号。
长处‌:
全局唯一性‌:在分布式体系中,雪花算法可以确保天生的ID全局唯一。
‌有序性‌:天生的ID按照时间戳有序递增,便于数据管理和查询。
‌高并发‌:每毫秒可以天生4096个ID,得当高并发场景。
‌缺点‌:
‌依靠服务器时间‌:如果服务器时间回拨,大概会导致天生重复的ID。可以通过记载末了一个天生ID的时间戳来办理这个标题。
‌序列号浪费‌:在分库分表时,如果序列号不停从0开始,大概会导致数据倾斜和不匀称分布。
`‌实用场景‌:
‌分布式体系‌:如分布式数据库、分布式锁等,必要全局唯一且有序的ID。
‌高并发场景‌:如订单号天生、用户ID天生等
  1. public class SnowflakeIdGenerator {
  2.     // 开始时间戳 (2023-01-01)
  3.     private final long twepoch = 1672502400000L;
  4.     // 机器ID所占的位数
  5.     private final long workerIdBits = 5L;
  6.     // 数据标识ID所占的位数
  7.     private final long datacenterIdBits = 5L;
  8.     // 支持的最大机器ID,结果是31 (这个值在位运算中不会溢出)
  9.     private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
  10.     // 支持的最大数据标识ID,结果是31
  11.     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
  12.     // 序列号在ID中所占的位数
  13.     private final long sequenceBits = 12L;
  14.     // 机器ID需要左移的位数,12
  15.     private final long workerIdShift = sequenceBits;
  16.     // 数据标识ID需要左移的位数,17
  17.     private final long datacenterIdShift = sequenceBits + workerIdBits;
  18.     // 时间戳需要左移的位数,22
  19.     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
  20.     // 生成序列的掩码,这里为4095 (0b111111111111=4095)
  21.     private final long sequenceMask = -1L ^ (-1L << sequenceBits);
  22.     // 工作机器ID(0~31)
  23.     private long workerId;
  24.     // 数据中心ID(0~31)
  25.     private long datacenterId;
  26.     // 毫秒内序列(0~4095)
  27.     private long sequence = 0L;
  28.     // 上次生成ID的时间戳
  29.     private long lastTimestamp = -1L;
  30.     public SnowflakeIdGenerator(long workerId, long datacenterId) {
  31.         if (workerId > maxWorkerId || workerId < 0) {
  32.             throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
  33.         }
  34.         if (datacenterId > maxDatacenterId || datacenterId < 0) {
  35.             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
  36.         }
  37.         this.workerId = workerId;
  38.         this.datacenterId = datacenterId;
  39.     }
  40.     /**
  41.      * 产生下一个ID
  42.      */
  43.     public synchronized long nextId() {
  44.         long timestamp = timeGen();
  45.         // 如果当前时间小于上次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
  46.         if (timestamp < lastTimestamp) {
  47.             throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
  48.         }
  49.         // 如果是同一时间生成的,则进行毫秒内序列
  50.         if (lastTimestamp == timestamp) {
  51.             sequence = (sequence + 1) & sequenceMask;
  52.             // 毫秒内序列溢出
  53.             if (sequence == 0) {
  54.                 // 阻塞到下一毫秒,获得新的时间戳
  55.                 timestamp = tilNextMillis(lastTimestamp);
  56.             }
  57.         } else {
  58.             // 时间戳改变,毫秒内序列重置
  59.             sequence = 0L;
  60.         }
  61.         // 上次生成ID的时间截
  62.         lastTimestamp = timestamp;
  63.         // 移位并通过或运算拼到一起组成64位的ID
  64.         return ((timestamp - twepoch) << timestampLeftShift)
  65.                 | (datacenterId << datacenterIdShift)
  66.                 | (workerId << workerIdShift)
  67.                 | sequence;
  68.     }
  69.     protected long tilNextMillis(long lastTimestamp) {
  70.         long timestamp = timeGen();
  71.         while (timestamp <= lastTimestamp) {
  72.             timestamp = timeGen();
  73.         }
  74.         return timestamp;
  75.     }
  76.     protected long timeGen() {
  77.         return System.currentTimeMillis();
  78.     }
  79. }
复制代码
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表