探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式

简介: 【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。

在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。

一、雪花算法原理

雪花算法由Twitter开源,其核心思想是通过一定的位运算,将时间戳、机器ID和序列号组合成一个64位的长整型ID。具体结构如下:

  1. 时间戳(41位):记录当前时间与特定起始时间(如Twitter使用的是2010-11-04)的差值,单位通常为毫秒,可使用69年。
  2. 机器ID(10位):支持部署最多1024个节点(包括机器和数据中心)。
  3. 序列号(12位):支持同一毫秒内生成最多4096个ID。

结构图如下:

复制代码
| 1 位符号位 | 41 位时间戳 | 10 位机器ID | 12 位序列号 |

二、时钟回拨问题

时钟回拨是指系统时钟由于某种原因(如人为调整、NTP同步错误等)突然倒退,这可能导致雪花算法生成的ID重复。处理时钟回拨的常见策略包括:

  1. 记录上一次生成ID的时间戳:每次生成ID时,比较当前时间戳与上一次的时间戳,如果检测到回拨,则拒绝生成ID或等待时间追上。
  2. 使用逻辑时钟:逻辑时钟保证总是递增,不依赖系统时钟。但需要额外的机制来同步和持久化逻辑时钟。

三、Java实现雪花算法

以下是雪花算法的Java实现,包括处理时钟回拨的逻辑:

java复制代码
public class SnowflakeIdGenerator {  
// 起始时间戳(2020-01-01 00:00:00 的 Unix 时间戳)  
private final long twepoch = 1577836800000L;  
// 机器ID所占的bit数  
private final long workerIdBits = 10L;  
// 数据中心ID所占的bit数  
private final long datacenterIdBits = 10L;  
// 支持的最大机器ID数量,结果为1024 (这个位数的机器ID最多1024个(0-1023))  
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);  
// 支持的最大数据中心ID数为1024  
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);  
// 序列在ID中占的位数  
private final long sequenceBits = 12L;  
// 机器ID向左移12位  
private final long workerIdShift = sequenceBits;  
// 数据中心ID向左移22位  
private final long datacenterIdShift = sequenceBits + workerIdBits;  
// 时间戳向左移22位  
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;  
// 生成序列的掩码,这里位运算保证只取12位  
private final long sequenceMask = -1L ^ (-1L << sequenceBits);  
private long workerId;  
private long datacenterId;  
private long sequence = 0L;  
private long lastTimestamp = -1L;  
public SnowflakeIdGenerator(long workerId, long datacenterId) {  
if (workerId > maxWorkerId || workerId < 0) {  
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));  
        }  
if (datacenterId > maxDatacenterId || datacenterId < 0) {  
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));  
        }  
this.workerId = workerId;  
this.datacenterId = datacenterId;  
    }  
// 产生下一个ID  
public synchronized long nextId() {  
long currentTimestamp = timeGen();  
if (currentTimestamp < lastTimestamp) {  
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - currentTimestamp));  
        }  
if (currentTimestamp == lastTimestamp) {  
// 如果在同一毫秒内  
            sequence = (sequence + 1) & sequenceMask;  
if (sequence == 0) {  
// 阻塞到下一个毫秒  
                currentTimestamp = tilNextMillis(lastTimestamp);  
            }  
        } else {  
            sequence = 0L;  
        }  
        lastTimestamp = currentTimestamp;  
return ((currentTimestamp - twepoch) << timestampLeftShift) |  
                (datacenterId << datacenterIdShift) |  
                (workerId << workerIdShift) |  
                sequence;  
    }  
// 阻塞到下一个毫秒,直到获得新的时间戳  
private long tilNextMillis(long lastTimestamp) {  
long timestamp = timeGen();  
while (timestamp <= lastTimestamp) {  
            timestamp = timeGen();  
        }  
return timestamp;  
    }  
// 获取当前时间戳  
private long timeGen() {  
return System.currentTimeMillis();  
    }  
public static void main(String[] args) {  
SnowflakeIdGenerator idWorker = new SnowflakeIdGenerator(1, 1);  
for (int i = 0; i < 10; i++) {  
long id = idWorker.nextId();  
            System.out.println(id);  
        }  
    }  
}

四、代码解释

  1. 常量定义
  • twepoch:起始时间戳。
  • workerIdBitsdatacenterIdBitssequenceBits:定义workerId、datacenterId和序列号占用的位数。
  • maxWorkerIdmaxDatacenterId:计算支持的最大workerId和datacenterId。
  • workerIdShiftdatacenterIdShifttimestampLeftShift:定义各部分在64位ID中的左移位数。
  • sequenceMask:序列号掩码,用于保证序列号不超过12位。
  1. 构造函数
  • 校验并初始化workerId和datacenterId。
  1. nextId方法
  • 获取当前时间戳,如果小于上一次时间戳,抛出异常处理时钟回拨。
  • 如果当前时间戳等于上一次时间戳,说明在同一毫秒内,序列号自增;如果序列号超过最大值(4095),则阻塞等待到下一毫秒。
  • 时间戳左移,并与datacenterId、workerId和序列号进行按位或运算,生成最终ID。
  1. tilNextMillis方法
  • 阻塞等待直到下一毫秒,以获取新的时间戳。
  1. timeGen方法
  • 获取当前系统时间戳。

五、总结

雪花算法通过时间戳、机器ID和序列号的组合,在分布式环境下生成全局唯一的64位ID。本文介绍了雪花算法的原理、处理了时钟回拨问题的策略,并提供了Java实现。这种算法不仅高效,而且保证了ID的有序性,是大数据量系统中常用的分布式ID生成方案。

相关文章
|
1月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
239 37
|
1月前
|
存储 缓存 NoSQL
【Java面试题汇总】Redis篇(2023版)
Redis的数据类型、zset底层实现、持久化策略、分布式锁、缓存穿透、击穿、雪崩的区别、双写一致性、主从同步机制、单线程架构、高可用、缓存淘汰策略、Redis事务是否满足ACID、如何排查Redis中的慢查询
【Java面试题汇总】Redis篇(2023版)
|
5天前
|
缓存 安全 Java
三万字长文Java面试题——基础篇(注:该篇博客将会一直维护 最新维护时间:2024年9月18日)
本文是一篇全面的Java面试题指南,涵盖了Java基础、数据类型、面向对象、异常处理、IO流、反射、代理模式、泛型、枚举、Lambda表达式、Stream流等多个方面的知识点,并提供了详细的解析和代码示例。
22 0
三万字长文Java面试题——基础篇(注:该篇博客将会一直维护 最新维护时间:2024年9月18日)
|
1月前
|
设计模式 安全 算法
【Java面试题汇总】设计模式篇(2023版)
谈谈你对设计模式的理解、七大原则、单例模式、工厂模式、代理模式、模板模式、观察者模式、JDK中用到的设计模式、Spring中用到的设计模式
【Java面试题汇总】设计模式篇(2023版)
|
1月前
|
存储 关系型数据库 MySQL
【Java面试题汇总】MySQL数据库篇(2023版)
聚簇索引和非聚簇索引、索引的底层数据结构、B树和B+树、MySQL为什么不用红黑树而用B+树、数据库引擎有哪些、InnoDB的MVCC、乐观锁和悲观锁、ACID、事务隔离级别、MySQL主从同步、MySQL调优
【Java面试题汇总】MySQL数据库篇(2023版)
|
5天前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
13 0
|
17天前
|
机器学习/深度学习 JavaScript 算法
面试中的网红虚拟DOM,你知多少呢?深入解读diff算法
该文章深入探讨了虚拟DOM的概念及其diff算法,解释了虚拟DOM如何最小化实际DOM的更新,以此提升web应用的性能,并详细分析了diff算法的实现机制。
|
19天前
|
消息中间件 NoSQL Java
Java知识要点及面试题
该文档涵盖Java后端开发的关键知识点,包括Java基础、JVM、多线程、MySQL、Redis、Spring框架、Spring Cloud、Kafka及分布式系统设计。针对每个主题,文档列举了重要概念及面试常问问题,帮助读者全面掌握相关技术并准备面试。例如,Java基础部分涉及面向对象编程、数据类型、异常处理等;JVM部分则讲解内存结构、类加载机制及垃圾回收算法。此外,还介绍了多线程的生命周期、同步机制及线程池使用,数据库设计与优化,以及分布式系统中的微服务、RPC调用和负载均衡等。
|
2月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
2月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。