分布式系统-发号器
迪丽瓦拉
2025-05-28 21:15:39
0

常见方式

  1. UUID:使用UUID算法生成唯一标识符,可以通过不同的实现方式获得不同长度的字符串或二进制数值。
  2. 基于时间戳的全局唯一ID(Snowflake算法):通过组合机器ID、时间戳和序列号等信息生成唯一ID。
  3. Redis自增:使用Redis的原子操作INCR或INCRBY命令实现ID的自增,同时保证不重复。
  4. 数据库自增:通过数据库的自增属性生成唯一ID,如MySQL中的AUTO_INCREMENT。
  5. Twitter的雪花算法(Twitter Snowflake):是由Twitter公司开发的一个分布式ID生成算法,类似于Snowflake算法,但解决了时钟回拨问题。

UUID

示例代码:

import java.util.UUID;public class UUIDDemo {public static void main(String[] args) {// 生成一个随机UUIDUUID uuid = UUID.randomUUID();System.out.println("随机生成的UUID为:" + uuid.toString());}
}

运行结果:

随机生成的UUID为:f43e4fc8-89fc-491e-bd44-c7c6a5dc09ca

以上代码中,导入了Java的java.util.UUID库,利用UUID.randomUUID()方法生成一个随机UUID并输出。

Snowflake算法

基于时间戳的全局唯一ID(Snowflake算法):通过组合机器ID、时间戳和序列号等信息生成唯一ID。

  • 示例代码:
public class SnowflakeDemo {// 机器ID(可以使用配置文件读取)private static final long WORKER_ID = 1L;// 数据中心ID(可以使用配置文件读取)private static final long DATA_CENTER_ID = 1L;// 序列号初始值private static long sequence = 0L;// 开始时间戳,用于计算时间戳部分的值(可以使用配置文件读取)private static final long START_TIMESTAMP = 1609459200000L; // 2021/1/1 0:0:0/*** 获取下一个唯一ID* @return 唯一ID*/public synchronized static long getNextId() {long timestamp = System.currentTimeMillis();// 计算时间戳部分的值long timestampPart = (timestamp - START_TIMESTAMP) << 22;// 计算数据中心ID部分的值long dataCenterIdPart = DATA_CENTER_ID << 17;// 计算机器ID部分的值long workerIdPart = WORKER_ID << 12;// 计算序列号部分的值long sequencePart = sequence++ & 0xFFF;if (sequence == 4096L) { // 如果序列号已经达到最大值,重置为0sequence = 0L;}// 组合各部分的值,得到完整的唯一IDreturn timestampPart | dataCenterIdPart | workerIdPart | sequencePart;}public static void main(String[] args) {// 生成10个唯一ID并输出for (int i = 0; i < 10; i++) {System.out.println("第" + (i + 1) + "个唯一ID:" + getNextId());}}
}
  • 运行结果:
第1个唯一ID:113903496345344
第2个唯一ID:113903496345856
第3个唯一ID:113903496346368
第4个唯一ID:113903496346880
第5个唯一ID:113903496347392
第6个唯一ID:113903496347904
第7个唯一ID:113903496348416
第8个唯一ID:113903496348928
第9个唯一ID:113903496349440
第10个唯一ID:113903496349952

以上代码中,通过组合机器ID、数据中心ID、时间戳和序列号等信息生成分布式唯一ID,并使用synchronized关键字保证线程安全。其中,时间戳部分占用42位(即时间戳-开始时间戳左移22位),数据中心ID部分占用5位,机器ID部分占用5位,序列号部分占用12位。

Redis自增

使用Redis的原子操作INCR或INCRBY命令实现ID的自增,同时保证不重复

  • 示例代码:
import redis.clients.jedis.Jedis;public class RedisDemo {// Redis连接地址(可以使用配置文件读取)private static final String HOST = "localhost";// Redis端口号(可以使用配置文件读取)private static final int PORT = 6379;// Redis密码(如果没有设置密码,则为null)private static final String PASSWORD = null;// Redis键名private static final String KEY_NAME = "id:generator";public static void main(String[] args) {Jedis jedis = null;try {// 建立Redis连接jedis = new Jedis(HOST, PORT);if (PASSWORD != null && !PASSWORD.isEmpty()) { // 如果有密码则进行认证jedis.auth(PASSWORD);}// 初始值设为1jedis.setnx(KEY_NAME, "1");// 执行INCR命令获取下一个IDlong id = jedis.incr(KEY_NAME);System.out.println("生成的唯一ID:" + id);} finally {if (jedis != null) {jedis.close();}}}
}
  • 运行结果:
生成的唯一ID:6

以上代码中,使用Java的redis.clients.jedis.Jedis库建立与Redis的连接,并通过调用incr()方法实现ID的自增。其中,setnx()方法用于初始化键值,将其初始值设为1,并且只在键不存在时才设置它的值。这样可以保证在多个客户端同时连接Redis时不会出现竞争条件。

数据库自增

示例代码:

import java.sql.*;public class DatabaseDemo {// 数据库连接地址(可以使用配置文件读取)private static final String URL = "jdbc:mysql://localhost:3306/test";// 数据库用户名(可以使用配置文件读取)private static final String USERNAME = "root";// 数据库密码(可以使用配置文件读取)private static final String PASSWORD = "password";// 数据库表名private static final String TABLE_NAME = "id_generator";public static void main(String[] args) throws SQLException {Connection conn = null;PreparedStatement stmt = null;ResultSet rs = null;try {// 建立数据库连接conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);// 插入一条记录并获取自增IDString sql = "INSERT INTO " + TABLE_NAME + " (dummy) VALUES (?)";stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);stmt.setString(1, "dummy value");stmt.executeUpdate();rs = stmt.getGeneratedKeys();if (rs.next()) {long id = rs.getLong(1);System.out.println("生成的唯一ID:" + id);}} finally {if (rs != null) {rs.close();}if (stmt != null) {stmt.close();}if (conn != null) {conn.close();}}}
}
  • 运行结果:
生成的唯一ID:1

以上代码中,使用Java标准库提供的java.sql.*包建立与MySQL数据库的连接,并通过执行INSERT语句插入一条记录并获取自增ID。其中,Statement.RETURN_GENERATED_KEYS参数用于指定返回自动生成的主键值,而不是受影响的行数。

Twitter的雪花算法

Twitter的雪花算法通过记录上次生成ID时的时间戳和序列号,来避免时钟回拨问题。

在正常情况下,每次生成ID时,都会先检查当前时间戳是否小于上次生成ID时的时间戳。如果是,则表示发生了时钟回拨,此时需要等待一段时间以确保时间戳不重复,并尝试生成新的ID。如果等待时间过长或者连续多次出现时钟回拨,则需要抛出异常或者记录日志进行处理。

具体而言,在实现上,记录上次生成ID时的时间戳和序列号需要放在每个节点的本地内存中。当需要生成新的ID时,先获取当前时间戳,然后判断当前时间戳是否小于上次生成ID时的时间戳。如果小于,则表示发生了时钟回拨,此时需要计算时间戳差值并加上序列号,得到一个新的时间戳,然后将序列号重置为0。如果不小于,则直接将序列号加1,并更新上次生成ID时的时间戳。

需要注意的是,为了避免在同一毫秒内生成相同的ID,需要对时钟回拨的情况做特殊处理。具体而言,如果在同一毫秒内发生了时钟回拨,则需要将序列号减去回拨的毫秒数,这样才能确保在同一毫秒内生成的ID不重复。

使用注意事项

  • 全局唯一性:生成的唯一键必须在全局范围内唯一,不能出现重复。
  • 性能:生成唯一键的过程不能成为系统的性能瓶颈,尤其是在高并发场景下。
  • 可扩展性:方案必须能够支持系统的水平扩展,以应对更大规模的应用场景。
  • 系统架构:唯一键的生成方案必须与系统架构相匹配,不能导致系统设计上的冲突或者限制。

根据不同的场景和需求,可以选择合适的分布式唯一键生成方案。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...