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

雪花算法是什么,时钟回拨问题怎么解决?

文章目录

      • 一、雪花算法的结构
      • 二、雪花算法的核心优势
      • 三、时钟回拨问题及解决方案
        • 什么是时钟回拨?
        • 解决方案
          • 1. 等待时间追平时钟(简单场景)
          • 2. 记录历史时间戳,使用最大时间+序列号(避免阻塞)
          • 3. 引入物理时钟+逻辑时钟(复杂场景)
          • 4. 预留机器ID位扩展(极端场景)
          • 5. 监控与告警
      • 四、总结

雪花算法(Snowflake)是一种分布式唯一ID生成算法,由Twitter设计,用于在分布式系统中生成全局唯一、有序递增的ID。它能满足高并发场景下的ID生成需求(如每秒生成数十万ID),且不依赖数据库等第三方组件。

一、雪花算法的结构

雪花算法生成的ID是一个64位的长整型(Long),结构如下(从高位到低位):

位数含义作用
1位符号位固定为0(保证ID为正数)
41位时间戳(毫秒级)记录ID生成的时间,精确到毫秒,可支持约69年(2^41 / 1000/60/60/24/365 ≈ 69)
10位机器ID用于区分不同机器/节点,最多支持1024个节点(2^10 = 1024)
12位序列号同一毫秒内同一机器生成的ID序号,最多支持4096个ID/毫秒(2^12 = 4096)

示例:一个雪花ID的二进制拆分可能为:
0(符号位) + 1001...1101(41位时间戳) + 0000110010(10位机器ID) + 000000100101(12位序列号)

二、雪花算法的核心优势

  1. 全局唯一:通过机器ID区分节点,同一节点内通过时间戳+序列号保证唯一性。
  2. 有序递增:ID随时间递增,适合数据库主键(有序插入可减少索引碎片)。
  3. 高性能:纯内存计算,无IO操作,单机每秒可生成数十万ID。
  4. 无中心化:无需依赖数据库或分布式协调工具(如ZooKeeper)。

三、时钟回拨问题及解决方案

雪花算法依赖服务器的系统时钟,若出现时钟回拨(系统时间被调回过去,如NTP时间同步、手动修改时间),可能导致生成重复ID(同一毫秒+同一机器ID+同一序列号)。这是雪花算法最核心的问题,需针对性解决。

什么是时钟回拨?

正常情况下,系统时间是单调递增的,但以下场景可能导致时间回拨:

  • 服务器同步NTP服务器时间时,本地时间快于标准时间,被强制调回。
  • 手动修改系统时间(如误操作将时间改到过去)。
  • 虚拟机/容器环境中,宿主机器时间调整导致内部时间回拨。
解决方案

针对时钟回拨,常见解决策略如下:

1. 等待时间追平时钟(简单场景)

当检测到当前时间小于最后一次生成ID的时间(发生回拨),则阻塞等待,直到系统时间超过最后一次时间。

示例代码片段

public synchronized long nextId() {long currentTime = System.currentTimeMillis();// 检测到时钟回拨if (currentTime < lastTimestamp) {// 计算回拨的毫秒数long offset = lastTimestamp - currentTime;// 若回拨时间较短(如小于5ms),等待时间追上if (offset <= 5) {try {// 等待offset毫秒,让系统时间超过lastTimestampThread.sleep(offset);currentTime = System.currentTimeMillis();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {// 回拨时间过长,抛出异常或采取其他策略throw new RuntimeException("时钟回拨过大,无法生成ID");}}// 同一毫秒内,序列号递增if (currentTime == lastTimestamp) {sequence = (sequence + 1) & sequenceMask; // sequenceMask=4095(2^12-1)// 序列号用完,等待下一毫秒if (sequence == 0) {currentTime = tilNextMillis(lastTimestamp);}} else {// 新的毫秒,序列号重置为0sequence = 0;}lastTimestamp = currentTime;// 组装ID:时间戳 << (10+12) | 机器ID << 12 | 序列号return (currentTime - epoch) << (workerIdBits + sequenceBits) | (workerId << sequenceBits) | sequence;
}// 等待到下一毫秒
private long tilNextMillis(long lastTimestamp) {long time = System.currentTimeMillis();while (time <= lastTimestamp) {time = System.currentTimeMillis();}return time;
}

适用场景:回拨时间短(如几毫秒),且业务可接受短暂阻塞(如非实时交易场景)。
缺点:若回拨时间长(如几秒),会导致长时间阻塞,影响服务可用性。

2. 记录历史时间戳,使用最大时间+序列号(避免阻塞)

当检测到时钟回拨时,不阻塞等待,而是复用最后一次的时间戳,并递增序列号(前提是序列号未用完)。

示例逻辑

if (currentTime < lastTimestamp) {// 回拨时,使用lastTimestamp,序列号继续递增if (sequence < sequenceMask) {sequence++;} else {// 序列号用完,只能抛出异常或等待throw new RuntimeException("时钟回拨且序列号耗尽");}
} else {// 正常逻辑(略)
}

适用场景:回拨时间短,且同一毫秒内序列号有剩余(如每秒生成ID不超过4096*1000)。
风险:若回拨时间长且序列号耗尽,仍会生成重复ID。

3. 引入物理时钟+逻辑时钟(复杂场景)

通过硬件时钟(如CPU的TSC寄存器)分布式时间服务获取更可靠的时间,避免依赖系统时钟。若系统时钟回拨,使用逻辑递增的时间戳(基于最后一次时间+1)。

实现思路

  • 维护一个“逻辑时间戳”,初始等于系统时间。
  • 每次生成ID时,取系统时间与逻辑时间戳的最大值作为当前时间戳。
  • 即使系统时间回拨,逻辑时间戳仍会单调递增,保证ID唯一性。
private long lastTimestamp; // 逻辑时间戳,初始为系统时间public synchronized long nextId() {long currentTime = System.currentTimeMillis();// 取系统时间与逻辑时间的最大值,保证单调递增currentTime = Math.max(currentTime, lastTimestamp);// 后续逻辑同雪花算法(序列号处理等)// ...lastTimestamp = currentTime; // 更新逻辑时间戳return ...;
}

优势:彻底避免时钟回拨导致的重复ID,适合对可用性要求高的场景(如金融交易)。

4. 预留机器ID位扩展(极端场景)

若回拨无法避免,可将部分机器ID位临时用作时间补偿(如牺牲2位机器ID,扩展时间戳范围),但会减少支持的节点数,需谨慎使用。

5. 监控与告警

无论采用哪种方案,都需监控系统时间,当检测到时钟回拨时(如回拨超过阈值),立即触发告警(如短信、邮件),让运维人员排查时间同步问题(如NTP配置错误)。

四、总结

雪花算法通过时间戳+机器ID+序列号生成全局唯一ID,核心问题是时钟回拨可能导致重复ID。实际应用中,可根据业务场景选择解决方案:

  • 中小规模、回拨风险低的场景:采用“等待时间追平”方案(简单易实现)。
  • 高并发、高可用场景:采用“逻辑时间戳”方案(避免阻塞,保证ID唯一性)。
  • 根本措施:加强服务器时间同步管理(如使用可靠的NTP服务),减少时钟回拨的发生。

此外,开源社区已有成熟的雪花算法变种(如百度UidGenerator、美团Leaf),内置了时钟回拨处理机制,可直接集成使用。

http://www.dtcms.com/a/360623.html

相关文章:

  • 大厂文章学习《DDD在大众点评交易系统演进中的应用》
  • 【数据分享】安徽省四份土地利用矢量shp数据
  • C++ 数据结构之哈希表及其相关容器
  • LeetCode 3459.最长 V 形对角线段的长度:记忆化搜索——就一步步试
  • 【开题答辩全过程】以 家庭理财管理系统的设计与实现为例,包含答辩的问题和答案
  • mit6.031 2023spring 软件构造 笔记 Testing
  • 自定义创建Linux内核Tracepoint
  • git的子模块讲解
  • mmaction安装的详细说明帖
  • 【ArcGIS微课1000例】0150:如何根据地名获取经纬度坐标
  • 基于springboot的摄影器材租赁回收系统
  • 疯狂星期四文案网第56天运营日记
  • LeetCode 36. 有效的数独 - 解题思路与实现详解
  • arnold图像加密(猫脸变换)
  • AIGC应用与实践 - 实验3:使用豆包生成播客
  • 赵玉平《刘备谋略》读书笔记(上部)
  • zookeeper集群是什么技术, 有什么作用
  • 第三阶梯:变动感知——在流沙之上,建造你的灯塔
  • 在开发过程中经常遇到 OOM(内存溢出)问题,如何解决?
  • __getitem__()方法的神奇
  • 【LeetCode修行之路】算法的时间和空间复杂度分析
  • 2000w 的数据量,mysql要进行几次IO操作,为什么
  • GEE 实战:Landsat 5 月度 NDVI 数据插值填补(以 8 月为例)_后附完整代码
  • sting模拟实现
  • 前后端联合实现多个文件上传
  • FastAPI 教程:构建高性能异步 API 服务
  • 石化设备健康管理平台:工业智能化转型的关键使能技术​
  • std::thread详解
  • Spring Boot单体项目整合Nacos
  • C++17 折叠表达式(Fold Expressions)详解