探讨java系统中全局唯一ID实现方案

news2024/11/27 21:44:50

为什么需要全局唯一ID

我们这里引用美团 Leaf 的场景介绍:在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一 ID 来标识一条数据或消息,数据库的自增 ID 显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一 ID 做标识。此时一个能够生成全局唯一ID 的系统是非常必要的。

UUID

UUID (Universally Unique Identifier),通用唯一识别码的缩写。UUID是由一组32位数的16进制数字所构成,所以UUID理论上的总数为 1632=2128,约等于 3.4 x 10^38。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。生成的UUID是由 8-4-4-4-12格式的数据组成,其中32个字符和4个连字符’ - ',一般我们使用的时候会将连字符删除 uuid.toString().replaceAll(“-”,“”)。
UUID的产生方式主要包括以下几种:

  1. 基于时间戳和MAC地址(UUID v1):这种方式结合了主机的网络接口卡MAC地址和当前的日期时间来生成UUID。由于包含了MAC地址,因此这种方式生成的UUID在一定程度上是可以被跟踪的,因为它隐含了生成UUID的设备信息。
  2. 基于随机数(UUID v4):这是目前最常用的UUID生成方式。它完全基于随机数或伪随机数,没有任何可以识别的个人或组织信息,因此是完全匿名的。这也是为什么它被广泛应用于需要高度安全性和隐私保护的场景。
  3. 基于命名空间的UUID(UUID v3、v5):这种方式基于MD5和SHA-1散列算法,通过将特定命名空间的标识符和名字进行散列来生成UUID。这种方法允许UUID基于给定的名字和命名空间来生成,确保不同命名空间下的UUID是唯一的。
  4. 基于硬件的UUID:某些系统可能还会根据其他硬件信息来生成UUID,例如硬盘序列号等。
    java实现的uuid为基于随机数,使用代码:
public static void main(String[] args) {

    //获取一个版本4根据随机字节数组的UUID。
    UUID uuid = UUID.randomUUID();
    System.out.println(uuid.toString().replaceAll("-",""));
}

虽然 UUID 生成方便,本地生成没有网络消耗,但是使用起来也有一些缺点:

  1. 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
  2. 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,暴露使用者的位置。
  3. 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能,可以查阅 Mysql 索引原理 B+树的知识。

使用redis实现

Redis实现分布式唯一ID主要是通过提供像 INCR 和 INCRBY 这样的自增原子命令,由于Redis自身的单线程的特点所以能保证生成的 ID 肯定是唯一有序的。以下代码通过引入jedis来实现:

import redis.clients.jedis.Jedis;

public class UniqueIdGenerator {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final String UNIQUE_ID_KEY = "unique_id";

    public static void main(String[] args) {
        Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
        long uniqueId = generateUniqueId(jedis);
        System.out.println("生成的唯一ID: " + uniqueId);
        jedis.close();
    }

    private static long generateUniqueId(Jedis jedis) {
        return jedis.incr(UNIQUE_ID_KEY);
    }
}

雪花算法-Snowflake

Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java中64bit的整数是Long类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。
具体实现步骤如下:

  • 第一位为未使用(符号位表示正数),接下来的41位为毫秒级时间(41位的长度可以使用69年)
  • 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)
  • 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
    在这里插入图片描述

以下是java实现版本:

public class Snowflake implements Serializable {
	private static final long serialVersionUID = 1L;


	/**
	 * 默认的起始时间,为Thu, 04 Nov 2010 01:42:54 GMT
	 */
	public static long DEFAULT_TWEPOCH = 1288834974657L;
	/**
	 * 默认回拨时间,2S
	 */
	public static long DEFAULT_TIME_OFFSET = 2000L;

	private static final long WORKER_ID_BITS = 5L;
	// 最大支持机器节点数0~31,一共32个
	@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
	private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
	private static final long DATA_CENTER_ID_BITS = 5L;
	// 最大支持数据中心节点数0~31,一共32个
	@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
	private static final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);
	// 序列号12位(表示只允许workId的范围为:0-4095)
	private static final long SEQUENCE_BITS = 12L;
	// 机器节点左移12位
	private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
	// 数据中心节点左移17位
	private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
	// 时间毫秒数左移22位
	private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
	// 序列掩码,用于限定序列最大值不能超过4095
	private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 4095

	/**
	 * 初始化时间点
	 */
	private final long twepoch;
	private final long workerId;
	private final long dataCenterId;
	private final boolean useSystemClock;
	/**
	 * 允许的时钟回拨毫秒数
	 */
	private final long timeOffset;
	/**
	 * 当在低频模式下时,序号始终为0,导致生成ID始终为偶数<br>
	 * 此属性用于限定一个随机上限,在不同毫秒下生成序号时,给定一个随机数,避免偶数问题。<br>
	 * 注意次数必须小于{@link #SEQUENCE_MASK},{@code 0}表示不使用随机数。<br>
	 * 这个上限不包括值本身。
	 */
	private final long randomSequenceLimit;

	/**
	 * 自增序号,当高频模式下时,同一毫秒内生成N个ID,则这个序号在同一毫秒下,自增以避免ID重复。
	 */
	private long sequence = 0L;
	private long lastTimestamp = -1L;

	/**
	 * 构造,使用自动生成的工作节点ID和数据中心ID
	 */
	public Snowflake() {
		this(IdUtil.getWorkerId(IdUtil.getDataCenterId(MAX_DATA_CENTER_ID), MAX_WORKER_ID));
	}

	/**
	 * 构造
	 *
	 * @param workerId 终端ID
	 */
	public Snowflake(long workerId) {
		this(workerId, IdUtil.getDataCenterId(MAX_DATA_CENTER_ID));
	}

	/**
	 * 构造
	 *
	 * @param workerId     终端ID
	 * @param dataCenterId 数据中心ID
	 */
	public Snowflake(long workerId, long dataCenterId) {
		this(workerId, dataCenterId, false);
	}

	/**
	 * 构造
	 *
	 * @param workerId         终端ID
	 * @param dataCenterId     数据中心ID
	 * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
	 */
	public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) {
		this(null, workerId, dataCenterId, isUseSystemClock);
	}

	/**
	 * @param epochDate        初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用
	 * @param workerId         工作机器节点id
	 * @param dataCenterId     数据中心id
	 * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
	 * @since 5.1.3
	 */
	public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
		this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET);
	}

	/**
	 * @param epochDate        初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用
	 * @param workerId         工作机器节点id
	 * @param dataCenterId     数据中心id
	 * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
	 * @param timeOffset       允许时间回拨的毫秒数
	 * @since 5.8.0
	 */
	public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {
		this(epochDate, workerId, dataCenterId, isUseSystemClock, timeOffset, 0);
	}

	/**
	 * @param epochDate           初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用
	 * @param workerId            工作机器节点id
	 * @param dataCenterId        数据中心id
	 * @param isUseSystemClock    是否使用{@link SystemClock} 获取当前时间戳
	 * @param timeOffset          允许时间回拨的毫秒数
	 * @param randomSequenceLimit 限定一个随机上限,在不同毫秒下生成序号时,给定一个随机数,避免偶数问题,0表示无随机,上限不包括值本身。
	 * @since 5.8.0
	 */
	public Snowflake(Date epochDate, long workerId, long dataCenterId,
					 boolean isUseSystemClock, long timeOffset, long randomSequenceLimit) {
		this.twepoch = (null != epochDate) ? epochDate.getTime() : DEFAULT_TWEPOCH;
		this.workerId = Assert.checkBetween(workerId, 0, MAX_WORKER_ID);
		this.dataCenterId = Assert.checkBetween(dataCenterId, 0, MAX_DATA_CENTER_ID);
		this.useSystemClock = isUseSystemClock;
		this.timeOffset = timeOffset;
		this.randomSequenceLimit = Assert.checkBetween(randomSequenceLimit, 0, SEQUENCE_MASK);
	}

	/**
	 * 根据Snowflake的ID,获取机器id
	 *
	 * @param id snowflake算法生成的id
	 * @return 所属机器的id
	 */
	public long getWorkerId(long id) {
		return id >> WORKER_ID_SHIFT & ~(-1L << WORKER_ID_BITS);
	}

	/**
	 * 根据Snowflake的ID,获取数据中心id
	 *
	 * @param id snowflake算法生成的id
	 * @return 所属数据中心
	 */
	public long getDataCenterId(long id) {
		return id >> DATA_CENTER_ID_SHIFT & ~(-1L << DATA_CENTER_ID_BITS);
	}

	/**
	 * 根据Snowflake的ID,获取生成时间
	 *
	 * @param id snowflake算法生成的id
	 * @return 生成的时间
	 */
	public long getGenerateDateTime(long id) {
		return (id >> TIMESTAMP_LEFT_SHIFT & ~(-1L << 41L)) + twepoch;
	}

	/**
	 * 下一个ID
	 *
	 * @return ID
	 */
	public synchronized long nextId() {
		long timestamp = genTime();
		if (timestamp < this.lastTimestamp) {
			if (this.lastTimestamp - timestamp < timeOffset) {
				// 容忍指定的回拨,避免NTP校时造成的异常
				timestamp = lastTimestamp;
			} else {
				// 如果服务器时间有问题(时钟后退) 报错。
				throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
			}
		}

		if (timestamp == this.lastTimestamp) {
			final long sequence = (this.sequence + 1) & SEQUENCE_MASK;
			if (sequence == 0) {
				timestamp = tilNextMillis(lastTimestamp);
			}
			this.sequence = sequence;
		} else {
			// issue#I51EJY
			if (randomSequenceLimit > 1) {
				sequence = RandomUtil.randomLong(randomSequenceLimit);
			} else {
				sequence = 0L;
			}
		}

		lastTimestamp = timestamp;

		return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT)
				| (dataCenterId << DATA_CENTER_ID_SHIFT)
				| (workerId << WORKER_ID_SHIFT)
				| sequence;
	}

	/**
	 * 下一个ID(字符串形式)
	 *
	 * @return ID 字符串形式
	 */
	public String nextIdStr() {
		return Long.toString(nextId());
	}

	// ------------------------------------------------------------------------------------------------------------------------------------ Private method start

	/**
	 * 循环等待下一个时间
	 *
	 * @param lastTimestamp 上次记录的时间
	 * @return 下一个时间
	 */
	private long tilNextMillis(long lastTimestamp) {
		long timestamp = genTime();
		// 循环直到操作系统时间戳变化
		while (timestamp == lastTimestamp) {
			timestamp = genTime();
		}
		if (timestamp < lastTimestamp) {
			// 如果发现新的时间戳比上次记录的时间戳数值小,说明操作系统时间发生了倒退,报错
			throw new IllegalStateException(
					StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
		}
		return timestamp;
	}

	/**
	 * 生成时间戳
	 *
	 * @return 时间戳
	 */
	private long genTime() {
		return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();
	}

}

百度-UidGenerator

百度的 UidGenerator 是百度开源基于Java语言实现的唯一ID生成器,是在雪花算法 snowflake 的基础上做了一些改进。UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略,适用于docker等虚拟化环境下实例自动重启、漂移等场景。
在实现上,UidGenerator 提供了两种生成唯一ID方式,分别是 DefaultUidGenerator 和 CachedUidGenerator,官方建议如果有性能考虑的话使用 CachedUidGenerator 方式实现。UidGenerator 依然是以划分命名空间的方式将 64-bit位分割成多个部分,只不过它的默认划分方式有别于雪花算法 snowflake。它默认是由 1-28-22-13 的格式进行划分。可根据你的业务的情况和特点,自己调整各个字段占用的位数。

  • 第1位仍然占用1bit,其值始终是0。
  • 第2位开始的28位是时间戳,28-bit位可表示2^28个数,这里不再是以毫秒而是以秒为单位,每个数代表秒则可用(1L<<28)/ (360024365) ≈ 8.51 年的时间。
  • 中间的 workId (数据中心+工作机器,可以其他组成方式)则由 22-bit位组成,可表示 2^22 = 4194304个工作ID。
  • 最后由13-bit位构成自增序列,可表示2^13 = 8192个数。

Medis 薄雾算法工程实践

https://github.com/asyncins/medis

薄雾算法 Mist 由书籍《Python3 反爬虫原理与绕过实战》的作者韦世东综合各大厂的技术点且考虑高性能分布式序列号生成器架构后设计的一款“递增态势、不依赖数据库、高性能且不受时间回拨影响”的全局唯一序列号生成算法。
Medis 是薄雾算法 Mist 的工程实践,其名取自 Mist 和 Redis。薄雾算法是一款性能强到令我惊喜的全局唯一 ID 算法。有了 Mist 和 Medis,你就拥有了和美团 Leaf、微信 Seqsvr、百度 UIDGenerator 性能相当(甚至超过)的全局唯一 ID 服务了。相比复杂的 UIDGenerator 双 Buffer 优化和 Leaf-Snowflake,薄雾算法 Mist 简单太多了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1448552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive3.1.2——企业级调优

前言 本篇文章主要整理hive-3.1.2版本的企业调优经验&#xff0c;有误请指出~ 一、性能评估和优化 1.1 Explain查询计划 使用explain命令可以分析查询计划&#xff0c;查看计划中的资源消耗情况&#xff0c;定位潜在的性能问题&#xff0c;并进行相应的优化。 explain执行计划…

C# EventHandler<T> 示例

新建一个form程序&#xff0c;在调试窗口输出执行过程&#xff1b; 为了使用Debug.WriteLine&#xff0c;添加 using System.Diagnostics; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using S…

【Qt】qt常用控件之QIcon 以及 qrc机制设置图片路径(QtCreator)

文章目录 1. QIcon / windowIcon2. setIcon() 与 setwindowIcon()2.1 setIcon() 介绍与使用2.2 setWindowIcon 介绍与使用 3. 路径问题 & qrc机制的引入3.1 绝对路径 / 相对路径 的问题3.2 qrc机制3.3 在QtCreator下利用qrc机制引入图片 1. QIcon / windowIcon QIcon QIco…

Nacos 的配置管理和配置热更新

一、配置管理的必要性 1. 存在问题 微服务重复配置过多维护成本高&#xff1a;将各个微服务的配置都写到配置管理服务中&#xff0c;单个微服务不去编写配置&#xff0c;而是到配置管理服务中读取配置&#xff0c;实现配置共享&#xff0c;便于修改和维护 业务配置经常变动&a…

七天入门大模型 :LLM和多模态模型高效推理实践

前两天&#xff0c;我们已分享了 七天入门大模型 &#xff1a;LLM大模型基础知识最全汇总七天入门大模型 &#xff1a;提示词工程 Prompt Engineering&#xff0c;最全的总结来了&#xff01; 今天我们继续分享相关内容&#xff0c;需要技术交流、答疑的小伙伴&#xff0c;可…

传统推荐算法库使用--mahout初体验

文章目录 前言环境准备调用混合总结 前言 郑重声明&#xff1a;本博文做法仅限毕设糊弄老师使用&#xff0c;不建议生产环境使用&#xff01;&#xff01;&#xff01; 老项目缝缝补补又是三年&#xff0c;本来是打算直接重写写个社区然后给毕设使用的。但是怎么说呢&#xff…

JavaScript中的Symbol:加密与安全性

JavaScript中的Symbol是一种唯一且不可变的数据类型&#xff0c;引入了一种新的基本数据类型&#xff0c;用于表示独一无二的标识符。在本文中&#xff0c;我们将深入介绍JavaScript中的Symbol&#xff0c;讨论如何将其应用于JS加密中&#xff0c;提供案例代码&#xff0c;并说…

每日一题(数字颠倒,单词倒排)

数字颠倒_牛客题霸_牛客网 (nowcoder.com) #include <stdio.h>int main() {char arr[100];gets(arr);int lenstrlen(arr);for(int ilen-1;i>0;i--){printf("%c",arr[i]);}return 0; } 单词倒排_牛客题霸_牛客网 (nowcoder.com) #include <stdio.h> #…

PLC_博图系列☞参数实例

PLC_博图系列☞参数实例 文章目录 PLC_博图系列☞参数实例背景介绍参数实例参数实例的工作原理创建参数实例将实例作为参数传送 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 参数实例 背景介绍 这是一篇关于PLC编程的文章&#xff0c;特别是关于西门子的博图软件…

如何使用idea连通服务器上的Redis(详细版本)

这里我使用的是阿里云的服务器 打开阿里云的安全组&#xff0c;设置端口为6379 在redis.conf文件中&#xff0c;注释bind 127.0.0.1 将protected-mode设置为no&#xff0c;即关闭保护模式 更改服务器中的防火墙&#xff0c;放行6379端口 # 放行端口 firewall-cmd --zo…

Three.js学习7:dat.GUI 参数控制

每个学 Three.js 的都被安利了 dat.GUI 吧&#xff1f; 我也不例外&#xff01; 今天就来了学习下 dat.GUI&#xff0c;并记录下来自己的学习成果。 一、什么是 dat.GUI? dat.GUI 是一个轻量级的图形用户界面库&#xff08;GUI 组件&#xff09;&#xff0c;使用这个库可以…

【Zigbee课程设计系列文章】Zigbee开发环境搭建

【Zigbee课程设计系列文章】Zigbee开发环境搭建 前言IAR 下载安装Z-Stack协议栈安装 &#x1f38a;项目专栏&#xff1a;【Zigbee课程设计系列文章】&#xff08;附详细使用教程完整代码原理图完整课设报告&#xff09; 前言 &#x1f451;由于无线传感器网络&#xff08;也即…

C++提高编程(黑马笔记)

C提高编程 模版 特点&#xff1a; 只是一个框架&#xff0c;不可以直接使用通用并不是万能的 泛型主要利用模版 函数模版 语法&#xff1a; template<typename T> 函数# include<iostream> using namespace std;template<typename T> void MySwap(T&a…

unity 点击事件

目录 点击按钮&#xff0c;显示图片功能教程 第1步添加ui button&#xff0c;添加ui RawImage 第2步 添加脚本&#xff1a; 第3步&#xff0c;把脚本拖拽到button&#xff0c;点击button&#xff0c;设置脚本的变量&#xff0c; GameObject添加 Component组件 点击按钮&am…

Vue学习笔记(三)常用指令、生命周期

Vue学习笔记&#xff08;三&#xff09;常用指令 vue指令&#xff1a;html标签上带有 v- 前缀的特殊属性&#xff0c;不同的指令具有不同的含义&#xff0c;可以实现不同的功能。 常用指令&#xff1a; 指令作用v-for列表渲染&#xff0c;遍历容器的元素或者对象的属性v-bind…

例39:使用List控件

建立一个EXE工程&#xff0c;在窗体上放一个文本框&#xff0c;一个列表框和三个按钮输入如下的代码&#xff1a; Sub Form1_Command1_BN_Clicked(hWndForm As hWnd, hWndControl As hWnd)List1.AddItem(Text1.Text)End SubSub Form1_Command2_BN_Clicked(hWndForm As hWnd, h…

【数据结构】链表OJ面试题5《链表的深度拷贝》(题库+解析)

1.前言 前五题在这http://t.csdnimg.cn/UeggB 后三题在这http://t.csdnimg.cn/gbohQ 给定一个链表&#xff0c;判断链表中是否有环。http://t.csdnimg.cn/Rcdyc 给定一个链表&#xff0c;返回链表开始入环的第一个结点。 如果链表无环&#xff0c;则返回 NULLhttp://t.cs…

电路设计(17)——乒乓球竞赛计分仪的proteus仿真

1.设计要求 设计、制作一台包括有“抛币选边”&#xff1b;“热身定时”&#xff1b;竞赛计分等电路的乒乓球竞赛计分仪&#xff0c;要求在同一块万能板上完成制作。 乒乓球开赛前&#xff0c;裁判员会叫双方有关运动员到身边商定采用抛硬币办法选边&#xff08;或选先发球&…

【开源】SpringBoot框架开发食品生产管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3 食品管理模块2.4 生产销售订单管理模块2.5 系统管理模块2.6 其他管理模块 三、系统展示四、核心代码4.1 查询食品4.2 查询加工厂4.3 新增生产订单4.4 新增销售订单4.5 查询客户 五、…

【Web】NSSCTF Round#18 Basic个人wp(部分)

目录 ①门酱想玩什么呢&#xff1f; ②Becomeroot ①门酱想玩什么呢&#xff1f; 先试一下随便给个链接 不能访问远程链接&#xff0c;结合评论区功能&#xff0c;不难联想到xss&#xff0c;只要给个评论区链接让门酱访问就可 我们研究下评论区 从评论区知道&#xff0c;要…