当前位置: 首页 > news >正文

雪花算法(Snowflake Algorithm)

雪花算法(Snowflake Algorithm)是一种分布式唯一ID生成算法,主要用于生成全球唯一的ID,广泛应用于分布式系统中,例如在数据库中作为主键。这个算法最初由Twitter提出,并且被广泛使用在很多大规模系统中。有以下特性:

唯一性: 确保生成的每一个ID都是全球唯一的,避免冲突。

时间有序性: 根据生成ID的时间戳,使得ID按时间顺序排列,方便进行时间排序和处理。

高效性: 在高并发环境下能够快速生成ID,通常在每毫秒内能够生成多个ID。

雪花算法的核心思想是将ID分解成几个部分,以确保生成的ID具有唯一性,同时又能保持一定的顺序性。它的ID结构一般包括以下几个部分:

时间戳(Timestamp): 用于表示当前时间的毫秒数。通常,时间戳占用最前面的一部分位数,这样可以保证ID在时间上有序。

数据中心ID(Data Center ID): 用于标识不同的数据中心,确保在不同的数据中心生成的ID不会冲突。

工作机器ID(Worker ID): 用于标识同一数据中心下的不同工作机器,确保同一数据中心内的不同机器生成的ID也不会冲突。

序列号(Sequence Number): 用于在同一毫秒内生成多个ID,以保证同一时间戳下生成的ID仍然唯一。

雪花算法通常生成的ID长度是64位,具体结构可能有所不同,但一个常见的结构是:

1位符号位(通常固定为0)

41位时间戳(表示毫秒数,一般支持约69年的时间范围)

10位数据中心ID和工作机器ID(分为5位数据中心ID和5位工作机器ID,支持最多1024个数据中心,每个数据中心支持最多1024台机器)

12位序列号(支持每毫秒生成4096个不同的ID)

这种设计可以保证生成的ID是全局唯一的,同时还能保持一定的顺序性,适用于高并发的分布式系统。

工作原理

生成ID时,算法会获取当前时间戳。

结合数据中心ID、工作机器ID以及序列号生成唯一ID。

如果在同一毫秒内生成多个ID,算法会增加序列号以确保唯一性。

在时间戳变化时,算法会重置序列号,并更新ID的时间戳部分。

优点有:

全局唯一性:‌能够在分布式系统中确保生成的ID全局唯一。‌

趋势递增性:‌生成的ID基本呈趋势递增,‌有利于数据库性能优化。‌

灵活性:‌可以根据业务特性灵活分配bit位。‌

高性能:‌不依赖数据库等第三方系统,‌以服务方式部署,‌生成ID的性能高。‌

缺陷有:‌

时钟回退问题:雪花算法依赖于系统时间戳来生成唯一 ID。如果系统时间被设置回过去,可能会导致生成重复的 ID 或者抛出异常。这是使用雪花算法时需要特别注意的问题,因为时钟回退会直接影响到 ID 的唯一性和生成稳定性。

时间戳依赖:雪花算法的 ID 生成是基于系统时间戳的。如果系统时间出现问题(如时钟漂移或系统时间错误),则会影响 ID 的生成。这使得系统时间的准确性和稳定性变得至关重要。

数据中心 ID 和工作机器 ID 的管理:需要合理分配和管理数据中心 ID 和工作机器 ID,以避免冲突。在大规模分布式系统中,如何分配和协调这些 ID 是一个挑战,错误的配置可能导致 ID 冲突或生成不一致。

高并发处理复杂性:在高并发场景下,雪花算法通过序列号来处理生成的 ID,但在极高负载情况下,仍然需要精细管理性能和同步问题。高并发可能导致性能瓶颈或增加实现的复杂性。

代码实现

public class SnowflakeIdGenerator {// 起始时间戳 (2020-01-01 00:00:00)private static final long START_TIMESTAMP = 1577836800000L;// 工作机器 ID 位数private static final long WORKER_ID_BITS = 5L;// 数据中心 ID 位数private static final long DATA_CENTER_ID_BITS = 5L;// 序列号位数private static final long SEQUENCE_BITS = 12L;// 工作机器 ID 最大值private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);// 数据中心 ID 最大值private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);// 序列号最大值private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 工作机器 ID 和数据中心 IDprivate final long workerId;private final long dataCenterId;// 序列号和上次时间戳private long sequence = 0L;private long lastTimestamp = -1L;/*** 构造函数,初始化工作机器 ID 和数据中心 ID。* * @param workerId 工作机器 ID* @param dataCenterId 数据中心 ID* @throws IllegalArgumentException 如果 workerId 或 dataCenterId 超出范围*/public SnowflakeIdGenerator(long workerId, long dataCenterId) {if (workerId > MAX_WORKER_ID || workerId < 0) {throw new IllegalArgumentException("Worker ID out of range");}if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {throw new IllegalArgumentException("Data Center ID out of range");}this.workerId = workerId;this.dataCenterId = dataCenterId;}/*** 生成下一个唯一的 ID。* * @return 生成的 ID*/public synchronized long nextId() {long timestamp = currentTimestamp();// 检查时钟是否回退if (timestamp < lastTimestamp) {throw new RuntimeException("Clock moved backwards. Refusing to generate id");}// 当前时间戳与上次时间戳相同,处理序列号溢出if (timestamp == lastTimestamp) {sequence = (sequence + 1) & SEQUENCE_MASK;if (sequence == 0) {timestamp = waitNextMillis(lastTimestamp);}} else {// 当前时间戳不同,重置序列号sequence = 0L;}lastTimestamp = timestamp;// 生成唯一 IDreturn ((timestamp - START_TIMESTAMP) << (WORKER_ID_BITS + DATA_CENTER_ID_BITS + SEQUENCE_BITS)) |(dataCenterId << (WORKER_ID_BITS + SEQUENCE_BITS)) |(workerId << SEQUENCE_BITS) |sequence;}/*** 等待直到下一个毫秒。* * @param lastTimestamp 上次生成 ID 的时间戳* @return 当前时间戳*/private long waitNextMillis(long lastTimestamp) {long timestamp = currentTimestamp();while (timestamp <= lastTimestamp) {timestamp = currentTimestamp();}return timestamp;}/*** 获取当前时间戳。* * @return 当前时间戳(毫秒)*/private long currentTimestamp() {return System.currentTimeMillis();}/*** 测试生成 ID。* * @param args 命令行参数*/public static void main(String[] args) {SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);for (int i = 0; i < 10; i++) {System.out.println(idGenerator.nextId());}}
}

http://www.lryc.cn/news/414563.html

相关文章:

  • 〖任务1〗ROS2 jazzy Linux Mint 22 安装教程
  • 图像增强:使用周围像素填充掩码区域
  • 给虚拟机Ubuntu扩展硬盘且不丢数据
  • Oracle(41)如何使用PL/SQL批量处理数据?
  • JavaEE 第2节 线程安全知识铺垫1
  • LeetCode Hot100 零钱兑换
  • 微信小程序接口实现语音转文字
  • [Spark Streaming] 读取 Kafka 消息, 插入到 MySQL
  • 精选3款国内wordpress 主题,建站首选
  • JavaScript之 Uint8Array 类型数组(solana pda场景中的大小端)
  • 《Windows API每日一练》24.1 WinSock简介
  • openwrt编译Dockerfile
  • 【C语言】分支与循环(循环篇)——结尾猜数字游戏实现
  • 【数据结构】链表篇
  • Python SciPy介绍
  • docker镜像源
  • 【clion】clion打开文件目录卡死问题
  • [CR]厚云填补_GridFormer
  • PostgreSQL数据库内核(二):通过initdb传递guc参数
  • rust常用的宏使用记录(九)
  • 【Python机器学习】支持向量机——手写数字识别问题
  • 学习笔记-Cookie、Session、JWT
  • 题海战术,面试必胜秘诀
  • 设计模式详解(十九)——命令模式
  • 实战:MySQL数据同步神器之Canal
  • 5.6软件工程-运维
  • 在JavaScript中如何确保构造函数只被new调用
  • 【数据结构算法经典题目刨析(c语言)】反转链表(图文详解)
  • 机器学习之争:Python vs R,谁更胜一筹?
  • Vulnhub靶机:JANGOW_ 1.0.1