java中使用雪花算法(Snowflake)为分布式系统生成全局唯一ID

news2024/11/26 13:51:48

(全局唯一ID的解决方案有很多种,这里主要是介绍和学习Snowflake算法)

什么是雪花算法(Snowflake)

雪花算法(Snowflake Algorithm)是由Twitter公司在2010年左右提出的一种分布式ID生成算法,主要用于生成全局唯一且趋势递增的ID。这种算法生成的ID是一个64位的长整型数字,具有很高的性能与扩展性,特别适合于分布式环境下的主键生成场景,比如数据库表主键、消息队列的Message ID等。

实现原理

Snowflake算法的原理主要体现在它生成64位ID的结构上,主要划分为如下几个部分:

0 | 00000000000000000000000000000000000000000 | 00000 | 00000 | 000000000000

  • 1bit-符号位:

        第1位通常固定为0,表示生成的ID都是正数。

  • 41bit-时间戳部分:

        从第2位到第42位(共41位)存储时间戳信息,精确到毫秒级别。时间戳可以是自定义的一个起始时间点(如Twitter使用的是2010-11-04的某一时刻),这样可以通过比较ID中的时间戳部分来判断事件发生的先后顺序。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69。

  • 10bit-工作机器ID(5bit数据中心ID+5bit机器ID):

        从第43位到第52位(共10位)存储工作机器ID或者数据中心ID。这部分可以进一步细分为两部分,例如前5位标识数据中心ID,后5位标识工作节点ID。这样可以支持32(0~31)个数据中心以及每个数据中心内部的32(0~31)个工作节点,足够覆盖大规模分布式系统的节点标识。

  • 12bit-序列号部分:

        从第53位到第64位(共12位)存储同一节点同一毫秒内生成的序列号,这意味着同一个节点在同毫秒内可以生成最多4096个不同的ID(2^12)。

当生成ID时,首先获取当前时间戳,然后加上工作节点ID以及序列号。如果在同一毫秒内有新的请求,则序列号加1。若序列号达到最大值,则等待下一毫秒再进行分配,从而确保在同一节点内生成的ID是唯一的

雪花算法的优缺点

优点:

  1. 全局唯一性:雪花算法生成的ID是全局唯一的,这在分布式系统中非常重要,可以避免因ID冲突而导致的数据不一致问题。

  2. 递增有序:由于ID中包含时间戳部分,所以生成的ID是递增有序的。这有助于数据库插入性能的优化,因为有序的ID可以减少数据库的页分裂,提高写入效率。

  3. 灵活性:雪花算法允许自定义配置工作机器ID和数据中心ID的位数,可以根据实际部署环境调整这些配置,以支持不同规模的分布式系统。

  4. 高效性:算法本身实现简单,生成ID的速度快,能够满足高并发场景下的需求。

缺点:

  1. 时钟依赖:雪花算法依赖于系统时钟来生成时间戳部分。如果系统时钟出现回拨或漂移,可能会导致生成的ID不唯一或有序性受到破坏。虽然可以通过一些机制来处理时钟回拨问题,但时钟漂移仍然是一个潜在的风险。

  2. 机器ID冲突:如果部署的工作节点数量超过了算法中定义的机器ID位数所能表示的范围,就会发生机器ID冲突。这需要在设计系统时预先规划好机器ID的分配和管理。

  3. 缺乏安全性:雪花算法生成的ID本身并不包含加密或签名信息,因此容易受到恶意篡改。如果ID的安全性要求较高,需要在生成ID后添加额外的加密或签名措施。

  4. 扩展性限制:由于雪花算法的ID结构是固定的,因此在某些情况下可能会受到扩展性的限制。例如,如果未来需要添加更多的元数据到ID中,或者需要支持更大的分布式系统规模,可能需要重新设计ID生成算法。

因此,为了更全面地解决雪花算法的缺陷问题,可能需要采取额外的措施,例如:

  • 增强时钟同步:使用NTP(Network Time Protocol)或其他时钟同步机制来确保各个节点之间的时钟尽可能准确同步。

  • 增加机器ID的灵活性:设计一种更灵活的方式来分配和管理机器ID,以便支持更多的工作节点和数据中心。

  • 安全性考虑:对生成的ID进行加密或签名,以防止恶意篡改。

综上所述,雪花算法在分布式系统中具有广泛的应用价值,其全局唯一性和递增有序性使得它成为生成唯一ID的优选方案之一。然而,在使用雪花算法时也需要注意其潜在的缺点,并根据实际需求进行配置和优化。

Snowflake算法生成ID的Java代码示例

以下是Snowflake算法的一个java简化版实现:

public class SnowflakeIdWorker {  
    // 起始的时间戳(自定义,例如系统上线时间)  
    private final long twepoch = 1288834974657L;  
  
    // 机器id所占的位数  
    private final long workerIdBits = 5L;  
  
    // 数据标识id所占的位数  
    private final long datacenterIdBits = 5L;  
  
    // 最大机器ID  
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);  
  
    // 最大数据标识ID  
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);  
  
    // 序列在id中占的位数  
    private final long sequenceBits = 12L;  
  
    // 机器ID左移12位  
    private final long workerIdShift = sequenceBits;  
  
    // 数据标识id左移17位(12+5)  
    private final long datacenterIdShift = sequenceBits + workerIdBits;  
  
    // 时间截左移22位(5+5+12)  
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;  
  
    // 序列的掩码,这里为4095 (0b111111111111=4095)  
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);  
  
    // 上次生成ID的时间截  
    private long lastTimestamp = -1L;  
  
    // 序列号  
    private long sequence = 0L;  
  
    // 工作机器ID  
    private final long workerId;  
  
    // 数据中心ID  
    private final long datacenterId;  
  
    public SnowflakeIdWorker(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 timestamp = timeGen();  
  
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退,抛出异常  
        if (timestamp < lastTimestamp) {  
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));  
        }  
  
        // 如果时间戳相同,则序列号自增  
        if (lastTimestamp == timestamp) {  
            sequence = (sequence + 1) & sequenceMask;  
            // 序列号溢出,等待下一毫秒  
            if (sequence == 0) {  
                timestamp = tilNextMillis(lastTimestamp);  
            }  
        } else {  
            // 时间戳改变,序列号重置为0  
            sequence = 0L;  
        }  
  
        // 更新最后的时间戳  
        lastTimestamp = timestamp;  
  
        // 移位并通过或运算拼到一起组成64位的ID  
        return ((timestamp - twepoch) << timestampLeftShift) |  
               (datacenterId << datacenterIdShift) |  
               (workerId << workerIdShift) |  
               sequence;  
    }  
  
    // 获取当前时间戳  
    protected long timeGen() {  
        return System.currentTimeMillis();  
    }  
  
    // 等待下一个毫秒  
    protected long tilNextMillis(long lastTimestamp) {  
        long timestamp = timeGen();  
        while (timestamp <= lastTimestamp) {  
            timestamp = timeGen();  
        }  
        return timestamp;  
    }

    public static void main(String[] args) {  
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);  
        for (int i = 0; i < 5; i++) {  
            long id = idWorker.nextId();  
            System.out.println(Long.toBinaryString(id));  
            System.out.println(id);  
        }  
    }  
}

代码输出:

这段代码实现了雪花算法的核心逻辑。在nextId()方法中,它首先获取当前时间戳,然后检查时间戳是否小于上一次生成ID时的时间戳,如果是,则抛出异常,因为这意味着系统时钟回退,可能会导致ID生成出现混乱。如果时间戳相同,则序列号自增,并检查是否溢出,如果溢出则等待下一个毫秒。如果时间戳不同,则重置序列号。最后,将时间戳、数据中心ID、机器ID和序列号按照各自的偏移量左移,然后进行位或运算,组合成一个64位的ID。

(注:关于数据中心ID、机器ID,根据实际情况来进行配置。)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1580072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

参加2023 甲骨文圆桌会议

2023年10月13日&#xff0c;我参加了2023 甲骨文圆桌会议&#xff0c;并做了《Oracle高可用架构的最佳实践》主题演讲。 照片为当时活动现场&#xff1a;

中霖教育:2024年注册计量师考试报名即将开始!

2024年注册计量师考试报名即将开始&#xff0c;时间定于4月中旬左右。对于考生来说&#xff0c;以下需要注意&#xff1a; 1、报名 考生需要在人事考试网站进行用户信息注册&#xff0c;在成功注册后&#xff0c;才能进一步进行报名操作。注册信息包括用户基本信息&#xff1…

【案例分享】如何通过甘特图管理项目进度?

我将通过一个实际案例来具体说明我是如何通过甘特图来管理项目进度的。 案例背景&#xff1a; 我负责过一个软件开发项目&#xff1a;一款在线学习APP。项目团队包括项目经理、开发人员、测试人员、UI设计师等多个角色&#xff0c;预计项目周期为6个月。 案例实施过程&…

Three.js--》实现2D转3D的元素周期表

今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多说直接开始。 目录 项目搭建 平铺元素周期表 螺旋元素周期表 网格元素周期表 球状元素周期表 加底部交互按钮 项目…

C语言操作符详解(二)

一、位操作符 & 按位与 | 按位或 ^ 按位异或 ~ 按位取反 注意&#xff1a;它们的操作数必须是整数。 下面的码我都只取了后八位 1.1、按位与 使用补码进行按位与 规则:对应二进制位有0就是0,两个同时为1才为1. 1.2、按位或 使用补码进行按位或 规则:对应二进…

Windows内核是什么,如何保障内核安全

Windows操作系统发展到如今已有三十余年&#xff0c;是目前在全球范围内广泛使用的操作系统。Windows内核是操作系统的核心部分&#xff0c;内核包括了HAL(硬件抽象层)&#xff0c;设备驱动&#xff0c;微内核&#xff0c;各种管理设备&#xff0c;管理层以及系统服务界面&…

【优选算法专栏】专题十六:BFS解决最短路问题(二)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

OpenHarmony实战:瑞芯微RK3566移植案例(下)

OpenHarmony实战&#xff1a;瑞芯微RK3566移植案例&#xff08;下&#xff09; OpenHarmony实战&#xff1a;瑞芯微RK3566移植案例&#xff08;中&#xff09; WIFI 整改思路及实现流程 整改思路 接下来熟悉HCS文件的格式以及"HDF WIFI”核心驱动框架的代码启动初始化…

Java入门基础知识第七课(超基础,超详细)——数组

前面二白讲了选择结构和循环结构&#xff0c;动手的同学会发现已经有了一定的难度&#xff0c;后面二白会专门收集一些经典的题目&#xff0c;训练多了才能让记忆更加深刻&#xff0c;这次咱们讲一下数组。 一、数组的定义 什么是数组呢&#xff0c;我们都知道变量是存储数据的…

第八讲 Sort Aggregate 算法

我们现在将讨论如何使用迄今为止讨论过的 DBMS 组件来执行查询。 1 查询计划【Query Plan】 我们首先来看当一个查询【Query】被解析【Parsed】后会发生什么&#xff1f; 当 SQL 查询被提供给数据库执行引擎&#xff0c;它将通过语法解析器进行检查&#xff0c;然后它会被转换…

新增长100人研讨会:20+上海医疗企业共探数字驱动下的目标管理与业绩增长策略

近日&#xff0c;纷享销客新增长100人系列活动之上海医疗专场&#xff0c;我们有幸邀请百趣生物一起&#xff0c;共同探讨医疗器械行业数字化增长的新理念、新方法和新实践。 活动聚集了百趣生物、汉维生物、松佰牙科器械、多宁生物、松佰医疗、瑞丰达医疗等20余位标杆医疗健康…

蓝桥杯简单模板

目录 最大公约数 两个数的最大公约数 多个数的最大公约数 最小公倍数 两个数的最小公倍数 多个数的最小公倍数 素数 ​编辑 位数分离 正写 ​编辑 反写 闰年 最大公约数 两个数的最大公约数 之前看见的是辗转相除法&#xff0c;例如现在让算一个49&#xff0c;21…

数码相框-LCD显示多行文字

显示几行文字: 从左显示&#xff1a;先描边再算出边框。居中显示&#xff1a;先算出边框&#xff0c;再确定坐标描画。 从左显示 第一行数据的起始位置是从(0,24)开始的。 要知道第二行数据从哪里开始&#xff0c;我们得知道画出来的矢量字体的边框是多少&#xff1a; 这个…

【C++】 详解 lower_bound 和 upper_bound 函数(看不懂来捶我!!!)

目录 一、前言 二、函数详解 &#x1f95d; lower_bound &#x1f34d;upper_bound 三、常考面试题 四、共勉 一、前言 这两个函数是我在 LeetCode 上做题见到&#xff0c;看到不熟悉的函数 lower_bound 和 upper_bound让我感觉很难受&#xff0c;于是在 C 官网去学习&…

2024HW --->反序列化漏洞!

对于反序列化&#xff0c;这个漏洞也是常用的&#xff0c;不过涉及到的方面非常非常广&#xff0c;比其他漏洞也难很多 于是本篇文章就分成PHP和JAVA的反序列化来讲讲 1.反序列化 想要理解反序列化&#xff0c;首先就要理解序列化 序列化&#xff1a;把对象转换为字节序列的过…

默克尔(Merkle)树 - 原理及用途

默克尔&#xff08;Merkle&#xff09;树的原理以及用途 引言 在当今数字化时代&#xff0c;确保数据的完整性是至关重要的。默克尔树作为一种高效的数据结构&#xff0c;被广泛应用于网络安全、分布式系统以及加密货币等领域&#xff0c;用于验证大量数据的完整性和一致性 数…

代码随想录算法训练营Day48|LC198 打家劫舍LC213 打家劫舍IILC337 打家劫舍III

一句话总结&#xff1a;前两题白给&#xff0c;第三题树形DP有点难。 原题链接&#xff1a;198 打家劫舍 滚动数组直接秒了。 class Solution {public int rob(int[] nums) {int n nums.length;int first 0, second nums[0];for (int i 2; i < n; i) {int tmp Math.m…

mega2560读取sick位移传感器

本次的项目中&#xff0c;需要使用到mega2560来读取sick位移传感器的模拟量&#xff0c;再把模拟量进行转换&#xff0c;从而使得到的数据为位移传感器的示数。 下面是位移传感器的接线图&#xff1a;棕色线接&#xff0b;24v&#xff0c;蓝色线接0v&#xff0c;白色线为模拟量…

JS 表单验证

点击注册的时候&#xff0c;渲染出来&#xff0c;验证码是自动获取出来的 html&#xff1a; <div class"div1">用户名<input type"text" id"yhm"><span id"span1"></span><br>密码<input type"…

mysql 查询变量@i:=@i+1

学习完mysql的查询&#xff1a;基本查询&#xff0c;连接查询和子查询和mysql 正则表达式查询&#xff0c;接下来先学习下变量查询。 mysql中没有oracle序列号那一列。mysql可以使用查询变量的方式去处理。我们先了解下查询变量&#xff0c;后面应用起来就更清晰。 1&#xff0…