SnowFlake 雪花算法和原理(分布式 id 生成算法)

news2024/11/17 17:36:56

一、概述

SnowFlake 算法:是 Twitter 开源的分布式 id 生成算法。

核心思想:使用一个 64 bit 的 long 型的数字作为全局唯一 id。

算法原理

  • 最高位是符号位,始终为0,不可用。

  • 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。

  • 10位的机器标识,10位的长度最多支持部署1024个节点

  • 12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号

算法优缺点

优点

  • 高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。

  • 基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增。

  • 不依赖第三方库或者中间件。

  • 算法简单,在内存中进行,效率高。

缺点

依赖服务器时间,服务器时钟回拨时可能会生成重复 id。算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。

二、算法实现

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>
public class IdWorker {

    //下面两个每个5位,加起来就是10位的工作机器id
    private long workerId;    //工作id
    private long datacenterId;   //数据id
    //12位的序列号
    private long sequence;

    public IdWorker(long workerId, long datacenterId, long sequence) {
        // sanity check for workerId
        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));
        }
        System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
                timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);

        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    //初始时间戳
    private long twepoch = 1288834974657L;

    //长度为5位
    private long workerIdBits = 5L;
    private long datacenterIdBits = 5L;
    //最大值
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    //序列号id长度
    private long sequenceBits = 12L;
    //序列号最大值
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    //工作id需要左移的位数,12位
    private long workerIdShift = sequenceBits;
    //数据id需要左移位数 12+5=17位
    private long datacenterIdShift = sequenceBits + workerIdBits;
    //时间戳需要左移位数 12+5+5=22位
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    //上次时间戳,初始值为负数
    private long lastTimestamp = -1L;

    public long getWorkerId() {
        return workerId;
    }

    public long getDatacenterId() {
        return datacenterId;
    }

    public long getTimestamp() {
        return System.currentTimeMillis();
    }

    //下一个ID生成算法
    public synchronized long nextId() {
        long timestamp = timeGen();

        //获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常
        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }

        //获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        //将上次时间戳值刷新
        lastTimestamp = timestamp;

        /**
         * 返回结果:
         * (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数
         * (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
         * (workerId << workerIdShift) 表示将工作id左移相应位数
         * | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。
         * 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id
         */
        return ((timestamp - 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) {
        IdWorker worker = new IdWorker(1, 1, 1);
        for (int i = 0; i < 30; i++) {
            System.out.println(worker.nextId());
        }
    }

}

解决时间回拨问题

原生的 Snowflake 算法是完全依赖于时间的,如果有时钟回拨的情况发生,会生成重复的 ID,市场上的解决方案也是不少。简单粗暴的办法有:

  • 最简单的方案,就是关闭生成唯一 ID 机器的时间同步。

  • 使用阿里云的的时间服务器进行同步,2017 年 1 月 1 日的闰秒调整,阿里云服务器 NTP 系统 24 小时“消化”闰秒,完美解决了问题。

  • 如果发现有时钟回拨,时间很短比如 5 毫秒,就等待,然后再生成。或者就直接报错,交给业务层去处理。也可以采用 SonyFlake 的方案,精确到 10 毫秒,以 10 毫秒为分配单元。

twitter的雪花算法:https://github.com/twitter-archive/snowflake

其它全局唯一的分布式ID的方式:如百度的uid-generator、美团的Leaf、滴滴的TinyId等

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

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

相关文章

Android 原生 TabLayout 使用全解析

前言为什么会有这篇文章呢&#xff0c;是因为之前关于TabLayout的使用陆陆续续也写了好几篇了&#xff0c;感觉比较分散&#xff0c;且不成体系&#xff0c;写这篇文章的目的就是希望能把各种效果的实现一次性讲齐&#xff0c;所以也有了标题的「看这篇就够了」。TabLayout作为…

【自然语言处理】Topic Coherence You Need to Know(主题连贯度详解)

Topic Coherence You Need to Know皮皮&#xff0c;京哥皮皮&#xff0c;京哥皮皮&#xff0c;京哥CommunicationUniversityofChinaCommunication\ University\ of\ ChinaCommunication University of China 在大多数关于主题建模的文章中&#xff0c;常用主题连贯度&#xff…

JSP实现数据传递与保存(一)

学习目标&#xff1a; 理解JSP内置对象的概念 掌握request和response的使用 掌握转发和重定向的区别 掌握out对象的使用 学习内容&#xff1a; 1.HTML页面转成JSP页面 HTML页面转成JSP页面一般有两种方式 方式1&#xff1a;直接修改HTML页面 1&#xff09;直接在HTM…

QT+OpenGL模板测试和混合

QTOpenGL模板测试和混合 本篇完整工程见gitee:QtOpenGL 对应点的tag&#xff0c;由turbolove提供技术支持&#xff0c;您可以关注博主或者私信博主 模板测试 当片段着色器处理完一个片段之后&#xff0c;模板测试会开始执行。和深度测试一样&#xff0c;它可能会丢弃片段&am…

Win11的两个实用技巧系列之Win11怎么找回Win7照片查看器

Win11怎么找回Win7照片查看器? Win11旧版照片查看器的切换方法Win11怎么找回Win7照片查看器&#xff1f;用习惯了win7的照片查看器&#xff0c;想要在win11中使用&#xff0c;该怎么启用旧版照片查看器呢&#xff1f;下面我们就来看看Win11旧版照片查看器的切换方法Win11系统启…

c++之二叉树【进阶版】

前言 在c语言阶段的数据结构系列中已经学习过二叉树&#xff0c;但是这篇文章是二叉树的进阶版&#xff0c;因为首先就会讲到一种树形结构“二叉搜索树”&#xff0c;学习二叉搜索树的目标是为了更好的理解map和set的特性。二叉搜索树的特性就是左子树键值小于根&#xff0c;右…

【JVM】运行时数据区与对象的创建流程

4、运行时数据区 4.1、运行时数据区介绍 运行时数据区也就是JVM在运⾏时产生的数据存放的区域&#xff0c;这块区域就是JVM的内存区域&#xff0c;也称为JVM的内存模型——JMM 堆空间&#xff08;线程共享&#xff09;&#xff1a;存放new出来的对象 元空间&#xff08;线程共…

3,预初始化(一)(大象无形9.2)

正如书中所说&#xff0c;预初始化流程由FEngineLoop::PreInit()所实现 主要处理流程 1&#xff0c;设置路径&#xff1a;当前程序路径&#xff0c;当前工作目录路径&#xff0c;游戏的工程路径 2,设置标准输出&#xff1a;设置GLog系统输出的设备&#xff0c;是输出到命令行…

web自动化测试-执行 JavaScript 脚本

JavaScript 是一种脚本语言&#xff0c;有的场景需要使用 js 脚本注入辅助我们完成 Selenium 无法做到的事情。 当 webdriver 遇到无法完成的操作时&#xff0c;可以使用 JavaScript 来完成&#xff0c;webdriver 提供了 execute_script() 方法来调用 js 代码。 执行 js 有两种…

Leetcode.2385 感染二叉树需要的总时间

题目链接 Leetcode.2385 感染二叉树需要的总时间 Rating &#xff1a; 1711 题目描述 给你一棵二叉树的根节点 root&#xff0c;二叉树中节点的值 互不相同 。另给你一个整数 start。在第 0分钟&#xff0c;感染 将会从值为 start的节点开始爆发。 每分钟&#xff0c;如果节点…

一文3000字从0到1实现基于requests框架接口自动化测试项目实战(建议收藏)

requests库是一个常用的用于http请求的模块&#xff0c;它使用python语言编写&#xff0c;在当下python系列的接口自动化中应用广泛&#xff0c;本文将带领大家深入学习这个库 Python环境的安装就不在这里赘述了&#xff0c;我们直接开干。 01、requests的安装 windows下执行…

大数据常见应用场景及架构改进

大数据常见应用场景及架构改进大数据典型的离线处理场景1.大数据数据仓库及它的架构改进2.海量数据规模下的搜索与检索3.新兴的图计算领域4.海量数据挖掘潜在价值大数据实时处理场景大数据典型的离线处理场景 1.大数据数据仓库及它的架构改进 对于离线场景&#xff0c;最典型…

磷脂-聚乙二醇-丙烯酸酯;DSPE-PEG-AC试剂说明;DSPE-PEG-Acrylate科研用

中文名称&#xff1a;磷脂-聚乙二醇-丙烯酸酯 丙烯酸酯-聚乙二醇-磷脂 简称&#xff1a;DSPE-PEG-AC&#xff1b;DSPE-PEG-Acrylate 溶剂&#xff1a;溶于部分常规有机溶剂 PEG分子量:1000&#xff1b;2000&#xff1b;3400&#xff1b;5000等等 注意事项&#xff1a;避免…

JavaSE02-JVM、JRE、JDK

文章目录一、JVM、JRE、JDK区别二、JDK的安装和配置1.JDK安装2.测试验证3.环境变量配置3.1 配置JAVA_HOME系统变量3.2 配置Path环境变量再最前面加上&#xff1a; %JAVA_HOME%\bin一、JVM、JRE、JDK区别 JVM&#xff08;Java Virtual Machine&#xff09;&#xff0c;Java虚拟…

jar包和AAR包

以前在使用 Eclipse 开发 Android 时&#xff0c;如果想代码打包&#xff0c;只有 jar 包一个方法&#xff0c;但是 jar包 只能把 Java 文件代码打包进去&#xff0c;如果要使用一个有布局和资源的库的话&#xff0c;除了将 jar 放入 libs 外,还要引入相关的资源和配置文件&…

详解一个TCP连接的建立与销毁

目录 &#x1f332; 图解TCP三次握手建立连接 TCP数据报结构 TCP连接的建立&#xff08;三次握手&#xff09; 最后的说明 &#x1f332; 详细分析TCP数据的传输过程 &#x1f332; 图解TCP四次握手断开连接 &#x1f332; 图解TCP三次握手建立连接 TCP&#xff08;Tran…

【模拟集成电路】宽摆幅压控振荡器(VCO)设计

鉴频鉴相器设计&#xff08;Phase Frequency Detector&#xff0c;PFD&#xff09;前言一、VCO工作原理二、VCO电路设计VCO原理图三、压控振荡器&#xff08;VCO&#xff09;测试VCO测试电路图瞬态测试&#xff08;1&#xff09;瞬态输出&#xff08;2&#xff09;局部放大图&a…

【Java】Spring Boot项目的创建和使用

文章目录SpringBoot的创建和使用1. 什么是Spring Boot&#xff1f;为什么要学Spring Boot&#xff1f;2. Spring Boot项目的优点3. Spring Boot 项目的创建3.1 使用idea创建3.2 接下来创建Spring Boot项目4. 项目目录介绍和运行4.1 运行项目4.2 输出内容5. 总结SpringBoot的创建…

nyist最终淘汰赛第一场

我出的题喜欢吗 我要水题解所以每一篇题解都分一个博客 A 题解链接: Atcoder abc257 E_霾まる的博客-CSDN博客 构造贪心题 在本次淘汰赛中较难 B 题解链接: atcoder abc217 D_霾まる的博客-CSDN博客 STL二分题, 当然你可以数组二分, 相对麻烦一点 在本次淘汰赛中较简单…

学习 Python 之 Pygame 开发魂斗罗(二)

学习 Python 之 Pygame 开发魂斗罗&#xff08;二&#xff09;魂斗罗的需求开始编写魂斗罗1. 搭建主类框架2. 设置游戏运行遍历和创建窗口3. 获取窗口中的事件4. 创建角色5. 完成角色更新函数魂斗罗的需求 魂斗罗游戏中包含很多个物体&#xff0c;现在要对这些物体进行总结 类…