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

雪花算法原理深度解析

雪花算法(Snowflake)是 Twitter 开源的分布式 ID 生成算法,用于在分布式系统中生成全局唯一且趋势递增的 ID。以下是对其简介、优缺点、原理及代码实现的详细解析:

一、雪花算法简介

        据国家大气研究中心的查尔斯·奈特称,一般的雪花大约由10^19个水分子组成。在雪花形成过程中,会形成不同的结构分支,所以说大自然中不存在两片完全一样的雪花,每一片雪花都拥有自己漂亮独特的形状。雪花算法表示生成的id如雪花般独一无二。

        snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。

二、雪花算法的优缺点

优点
  1. 全局唯一性:通过数据中心 ID 和机器 ID 确保不同节点生成的 ID 不重复。
  2. 趋势递增:生成的 ID 按时间有序递增,有利于数据库索引优化。
  3. 高性能:本地生成,无需依赖数据库或分布式协调服务,QPS 理论值约为 409.6 万 / 秒。
  4. 灵活可配置:可根据业务需求调整各部分位数(如增加机器 ID 位数、减少序列号位数)。
缺点
  1. 依赖系统时钟:若服务器时钟回拨,可能导致生成重复 ID(需额外处理)。
  2. ID 位数限制:64 位 ID 的各部分长度固定,需提前规划各字段的分配。
  3. 单节点生成上限:每毫秒最多生成 4096 个 ID,高并发场景可能需多节点扩展。

三、雪花算法原理

1)1位保留 (基本不用)

1位标识:由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0,所以这第一位都是0。

2)41位时间戳 

接下来 41 位存储毫秒级时间戳,41位可以表示2^41-1个毫秒的值,转化成单位年则是:(2^41−1)/(1000∗60∗60∗24∗365)=69年 。

41位时间戳 :也就是说这个时间戳可以使用69年不重复,大概可以使用 69 年。

注意:41位时间截不是存储当前时间的时间截,而是存储时间截的差值“当前时间截 – 开始时间截”得到的值。

这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的,一般设置好后就不要去改变了,切记!!!

因为,雪花算法有如下缺点:依赖服务器时间,服务器时钟回拨时可能会生成重复 id

3)10位机器

        10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId,最多可以部署 2^10=1024 台机器。这里的5位可以表示的最大正整数是2^5−1=31,即可以用0、1、2、3、….31这32个数字,来表示不同的datecenterId(数据中心ID),或workerId(工作机器ID)
 

4) 12bit序列号2

用来记录同毫秒内产生的不同id,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号。

QPS(每秒查询率)的计算基于 “单台机器每秒可生成的最大 ID 数”:

  • 1 秒 = 1000 毫秒
  • 每毫秒最多生成 4096 个 ID
  • 单台机器理论 QPS = 4096×1000=4,096,000(即 409.6 万 / 秒)。

这一数值是单节点的极限性能,实际中受系统处理能力(如 CPU、内存)限制,可能无法达到,但足以支撑绝大多数高并发场景(例如秒杀、峰值订单处理等)。

四、雪花算法代码实现(java)

import java.lang.management.ManagementFactory;
import java.util.concurrent.locks.ReentrantLock;/*** 雪花算法** @author sheng*/
public class Snowflake {public static final Snowflake INSTANCE = new Snowflake(getMachineId());// 自定义时间戳基准 2024 年 1 月 1 日)private static final long EPOCH = 1704067200000L;// 默认机器 IDprivate static final long DEFAULT_MACHINE_ID = 1L;// 时间戳位数private static final long TIMESTAMP_BITS = 41L;// 机器 ID 位数private static final long MACHINE_ID_BITS = 10L;// 递增序列号位数private static final long SEQUENCE_BITS = 12L;// 机器 ID 左移位数private static final long MACHINE_ID_LEFT_SHIFT = SEQUENCE_BITS;// 时间戳左移位数private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;// 最大时间戳private static final long MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) - 1;// 最大机器 IDprivate static final long MAX_MACHINE_ID = (1 << MACHINE_ID_BITS) - 1;// 最大序列号private static final long MAX_SEQUENCE = (1 << SEQUENCE_BITS) - 1;private final long machineId; // 机器 IDprivate long lastTimestamp = -1L;  // 上次生成 ID 的时间戳private long sequence = 0L;   // 递增序列号private final ReentrantLock lock = new ReentrantLock();// 私有化构造方法private Snowflake(long machineId) {// 校验机器 IDif (machineId < 0 || machineId > MAX_MACHINE_ID) {throw new IllegalArgumentException(String.format("MachineId 必须在 0 - %d 之间", MAX_MACHINE_ID));}this.machineId = machineId;}/*** 生成唯一 ID*/public long nextId() {lock.lock();try {long timestamp = currentTimeMillis();// 检查时间是否回拨if (timestamp < lastTimestamp) {throw new RuntimeException("系统时钟回退,拒绝生成 ID");}// 检查时间戳是否溢出long relativeTimestamp = timestamp - EPOCH;if (relativeTimestamp > MAX_TIMESTAMP) {throw new RuntimeException("时间戳超出可用范围");}// 同一毫秒内递增序列号if (timestamp == lastTimestamp) {sequence = (sequence + 1) & MAX_SEQUENCE;// 序列号溢出,等待下一毫秒if (sequence == 0) {timestamp = waitForNextMillis(lastTimestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;// 生成雪花算法 IDreturn  (relativeTimestamp << TIMESTAMP_LEFT_SHIFT)| (machineId << MACHINE_ID_LEFT_SHIFT)| sequence;} finally {lock.unlock();}}/*** 获取当前时间戳(毫秒)*/private long currentTimeMillis() {return System.currentTimeMillis();}/*** 等待下一毫秒*/private long waitForNextMillis(long lastTimestamp) {long timestamp = currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = currentTimeMillis();}return timestamp;}/*** 机器 ID* 使用进程 ID*/private static long getMachineId() {try {// 格式:<pid>@<hostname>String name = ManagementFactory.getRuntimeMXBean().getName();String pid = name.split("@")[0];// 保留后十位return Long.parseLong(pid) & MAX_MACHINE_ID;} catch (Exception e) {throw new RuntimeException("获取机器 ID 失败", e);}}}

测试

/*** @author sheng*/
public class SnowflakeIdTest {public static void main(String[] args) {Snowflake snowflake = Snowflake.INSTANCE;for (int i = 0; i < 100; i++) {System.out.println(snowflake.nextId());}}}

结果

130472881724493824
130472881728688128
130472881728688129
130472881728688130
130472881728688131
130472881728688132
130472881728688133

五、注意事项

  1. 机器 ID 分配:需确保每个节点的 datacenter_id 和 worker_id 组合唯一,可通过配置文件或分布式协调服务(如 ZooKeeper)分配。
  2. 时钟同步:建议所有服务器通过 NTP 服务定期同步时间,减少时钟回拨风险。
  3. 性能优化:高并发场景下,可通过预分配多个 Snowflake 实例(每个线程一个)减少锁竞争。
http://www.lryc.cn/news/601743.html

相关文章:

  • 【0基础PS】PS工具详解--选择工具--快速选择工具
  • 【n8n教程笔记——工作流Workflow】文本课程(第一阶段)——5.4 计算预订订单数量和总金额 (Calculating booked orders)
  • 使用Python,OpenCV,K-Means聚类查找图像中最主要的颜色
  • Unity Catalog与Apache Iceberg如何重塑Data+AI时代的企业数据架构
  • 【LeetCode 热题 100】35. 搜索插入位置——二分查找(左闭右开)
  • 高格办公空间:以 “空间为基,服务为翼”,重塑办公场景生态
  • 【语义分割】记录2:yolo系列
  • libomxil-bellagio移植到OpenHarmony
  • java小白闯关记第一天(两个数相加)
  • Python-初学openCV——图像预处理(三)
  • XSS利用
  • Web-Machine-N7靶机攻略
  • 文件权限标记机制在知识安全共享中的应用实践
  • JavaEE初阶第十二期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(十)
  • C++学习(线程相关)
  • 05 - spring security权限控制
  • Java Ai(day04)
  • [Linux入门] Linux 远程访问及控制全解析:从入门到实战
  • 【工具】python汇总发票(含源码)
  • InfluxDB 与 MQTT 协议集成实践(二)
  • Linux网络-------2.应⽤层⾃定义协议与序列化
  • 基于深度学习的图像分割:使用DeepLabv3实现高效分割
  • 【C语言网络编程】HTTP 客户端请求(基于 Socket 的完整实现)
  • 程序代码篇---python向http界面发送数据
  • 【QT入门到晋级】window opencv安装及引入qtcreator(包含两种qt编译器:MSVC和MinGW)
  • 字节前端面试知识点总结
  • 应对反爬机制的具体方法与策略
  • 《 接口日志与异常处理统一设计:AOP与全局异常捕获》
  • Android 调试桥 (adb) 基础知识点
  • 【C 学习】02-究竟什么是C?