Snowflake雪花算法

news2025/1/15 23:29:06

文章目录

  • 分布式ID
  • 一、雪花算法起源
  • 二、雪花算法的原理
  • 三、java实现雪花算法
  • 四、常见问题
  • 总结


分布式ID

分布式ID,也称为全局唯一ID,是在分布式系统中用于标识数据的唯一标识符。随着业务量的不断扩展,传统的UUID和数据库自增ID无法满足需求,需要进行分库分表,而分表后,每个表中的数据都会按自己的节奏进行自增,很有可能出现ID冲突。此时就需要一个单独的机制来负责生成唯一ID,生成出来的ID也可以叫做分布式ID,或全局ID。这个ID应满足全局唯一性、高性能和趋势递增等要求。


一、雪花算法起源

Snowflake中文的意思是“雪花”(因为在大自然中,不可能存在两片一模一样的雪花),所以被翻译成雪花算法。它最早是twitter内部使用的分布式环境下的唯一ID生成算法,在2014年开源。

二、雪花算法的原理

Snowflake产生的ID由 64 bit 的二进制数字组成,被分成了4个部分,每一部分存储的数据都有特定的含义:

>0 位: 符号位(标识正负),始终为 0>1~41 位 :一共 41 位,用来表示时间戳,单位是毫秒,可以支撑2 ^41 毫秒(约 69 年)2^41/1000*60*60*24*365 = 69>42~52 位 :一共 10 位,工作机器id,一般用前 5 位表示机房ID,后 5 位表示机器ID,用于区分不同集群/机房的节点,10位的长度,可以表示1024个不同节点。 
>53~64 位 :一共12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大ID(2^12 =4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID,最大可以支持400w左右的并发量。

具体结构如下:
在这里插入图片描述

三、java实现雪花算法

package snow;

/**
 * @author 杨树林
 * @version 1.0
 * @since 24/10/2023
 */

public class SnowFlake {
    // 机房(数据中心)ID
    private long datacenterId;

    // 机器ID
    private long workerId;

    // 同一时间的序列号
    private long sequence;

    // 开始时间戳
    private long twepoch = 1634393012000L;

    // 机房ID所占的位数: 5个bit
    private long datacenterIdBits = 5L;

    // 机器ID所占的位数:5个bit
    private long workerIdBits = 5L;

    // 最大机器ID:5bit最多只能有31个数字,就是说机器id最多只能是32以内
    // 最大:11111(2进制) --> 31(10进制)
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);

    // 最大机房ID:5bit最多只能有31个数字,机房id最多只能是32以内
    // 最大:11111(2进制)--> 31(10进制)
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    // 同一时间的序列所占的位数:12个bit
    // 最大111111111111 = 4095  代表同一毫秒最多生成4096个
    private long sequenceBits = 12L;

    // workerId的偏移量12
    private long workerIdShift = sequenceBits;

    // datacenterId的偏移量12+5
    private long datacenterIdShift = sequenceBits + workerIdBits;

    // timestampLeft的偏移量12+5+5
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    // 序列号掩码 4095 (0b111111111111=0xfff=4095)
    // 用于序号的与运算,保证序号最大值在0-4095之间
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    // 最近一次时间戳
    private long lastTimestamp = -1L;

    // 按照机器ID和机房ID创建雪花算法对象
    public SnowFlake(long workerId, long datacenterId) {
        this(workerId, datacenterId, 0);
    }

    public SnowFlake(long workerId, long datacenterId, long sequence) {
        // 合法判断
        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;
    }

    // 获取下一个随机的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));
        }

        // 同一毫秒并发
        if (lastTimestamp == timestamp) {
            // 同一毫秒(时间戳)内进行序列号递增
            sequence = (sequence + 1) & sequenceMask;

            // sequence序列大于4095,导致溢出
            if (sequence == 0) {
                // 调用到下一个时间戳的方法
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 如果是当前时间的第一次获取,那么就置为0
            sequence = 0;
        }

        // 记录上一次的时间戳
        lastTimestamp = timestamp;

        // 偏移计算
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    // 计算时间戳
    private long tilNextMillis(long lastTimestamp) {
        // 获取最新时间戳
        long timestamp = timeGen();

        // 如果发现最新的时间戳小于或者等于序列号已经超4095的那个时间戳
        while (timestamp <= lastTimestamp) {
            // 不符合则继续
            timestamp = timeGen();
        }
        return timestamp;
    }

    // 获取当前时间戳
    private long timeGen() {
        return System.currentTimeMillis();
    }


}


测试:

class test{
    public static void main(String[] args) {
        SnowFlake worker = new SnowFlake(1, 1);
        for (int i = 0; i < 100; i++) {
            System.out.println(worker.nextId());
        }
        System.out.println();
        worker = new SnowFlake(1, 2);
        for (int i = 0; i < 100; i++) {
            System.out.println(worker.nextId());
        }
    }
}

四、常见问题

1.时钟回拨?
计算机在获取时间戳时,一般时不会获取到前一刻的时间的,如果获取到的时间戳属于之前的一个时间点,这种现象叫做时间回拨。一般是由于人为原因对系统时间修改,或者不同服务器之间的时间有一些偏差导致的
2.第一位为什么不使用?
在计算机的表示中,第一位是符号位,0表示整数,第一位如果是1则表示负数,我们用的ID默认就是正数,所以默认就是0,那么这一位默认就没有意义。
3.twepoch表示什么?
由于时间戳只能用69年,计算机默认计时是从1970年开始的,所以使用twepoch表示从项目开始的时间,用生成ID的时间减去twepoch作为时间戳,可以使用更久。


总结

Snowflake雪花算法的使用场景一般在于分布式架构下解决分布式ID唯一性、安全性、具有实际意义性的一个算法,在很多大型分布式架构中都是非常重要的解决ID唯一性算法。

  • 第1位为符号位,固定为0;
  • 接下来的41位为时间戳(毫秒级),记录了生成ID的时间;
  • 然后是10位的机器ID,5位数据中心ID和5位工作机器ID,用于标识不同的机器;
  • 最后是12位的序列号,用于表示在同一毫秒内生成的多个ID的顺序。

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

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

相关文章

Linux下的IMX6ULL——IMX6ULL Pro开发板基本操作(二)

目录&#xff1a; 前面我们已经配置好了环境&#xff0c;下载好了软件&#xff0c;下面让我们对我们使用的开发板有给简单的了解吧&#xff0c;IMX6ULL Pro开发板基本操作是必不可少的一大环节&#xff0c;下面让我们从它的硬件资源&#xff0c;软件资源等一系列开发板的基础操…

医院多维度综合绩效考核系统源码 商业项目源码,支持二次开发

医院多维度综合绩效考核系统源码 商业项目源码&#xff0c;支持二次开发 采用多维度综合绩效考核的形式&#xff0c;针对院内实际情况分别对工作量、KPI指标、科研、教学、管理等进行全面考核。医院可结合实际需求&#xff0c;对考核方案中各维度进行灵活配置&#xff0c;对各维…

Kubernetes-进阶(Pod生命周期/调度/控制器,Ingress代理,数据存储PV/PVC)

Kubernetes-进阶 Pod详解 每个Pod中都可以包含一个或多个容器&#xff0c;这些容器可以分两类 用户程序所在容器&#xff0c;数量用户决定Pause容器&#xff0c;这是每个Pod都会有的一个根容器&#xff0c;它的作用有两个 可以以它为依据&#xff0c;评估整个Pod的健康状态可以…

打印机提示“需要用户干预”解决方法

在计算机管理中&#xff0c;点击“服务和应用程序”中的“服务”&#xff1b; 3、在右侧找到“Print Spooler”服务&#xff0c;点击停止此服务&#xff08;不要退出此页面&#xff0c;后面会用&#xff0c;最小化即可&#xff09;&#xff1b; 4、按WINR&#xff0c;输入“spo…

海信商用显示出海提速,在参与共建“一带一路”中成长

2023年是中国“一带一路”倡议提出十周年。十年来&#xff0c;“一带一路”建设取得了实打实、沉甸甸的重大历史性成就&#xff0c;成为深受欢迎的国际公共产品和国际合作平台。丰硕成果的背后&#xff0c;离不开广大企业的积极参与与担当作为。作为较早出海的中国品牌之一&…

发现一款好用的数据库安全风险扫描工具-DPEasy,推荐给大家!

数据库安全治理是确保企业数据安全的关键任务&#xff0c;然而&#xff0c;许多企业在实际操作中面临着多种挑战。通过深入沟通交流和实地分析&#xff0c;我们发现企业在数据库安全治理过程中主要受到以下问题困扰&#xff1a;外部人员风险、账号权限管理困难、内部业务复杂以…

安美数字酒店宽带运营系统SQL注入漏洞复现 [附POC]

文章目录 安美数字酒店宽带运营系统SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 安美数字酒店宽带运营系统SQL注入漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用文章内的相关技术从事非…

教你看懂Web 3.0:未来的互联网

众所周知&#xff0c;互联网已经成为我们日常生活的一部分&#xff0c;我们用它来搜索信息、社交、购物&#xff0c;甚至观看视频和听音乐。但你是否曾经想过&#xff0c;未来的互联网会是什么样子&#xff1f;让我们来谈谈Web 3.0&#xff0c;它是互联网的下一个大版本&#x…

BUGKU - 渗透测试1

场景1 源代码 场景2 admin 进入后台 账号密码admin admin 场景3 这里确实没想到。。。。 去php执行的地方 然后打开开发者工具进行抓包 发现执行后是存入php文件 我们写入一句话即可 这里我上传哥斯拉链接即可 场景4 根据提示 去找到数据库链接功能 场景5 PWN题目。…

k8s安装可视化界面

执行以下命令&#xff08;需要翻墙&#xff09;&#xff1a; kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml 或者用我文章提供的文件&#xff0c;执行 kubectl apply -f dashboard.xml 添加可访问地址 此时可…

融云AIGC专题:高知识密度与大数据处理双向奔赴的「金融大模型」

融云出海方案全线升级 点击上方小程序报名「爱嗨游」线上发布会 “怎么用大语言模型去提升生产效率和服务表现&#xff1f;”在时代交替之际&#xff0c;这是每个行业都要回答的问题。关注【融云 RongCloud】&#xff0c;了解协同办公平台更多干货。 而新技术的渗透不会在所有…

达芬奇MacOS最新中文版 DaVinci Resolve Studio 18中文注册秘钥

DaVinci Resolve Studio 18是一款专业的视频编辑软件&#xff0c;它具有多种强大的功能。首先&#xff0c;它提供了丰富的视频剪辑工具&#xff0c;如剪切、复制、粘贴、剪辑、缩放和移动等&#xff0c;使用户可以轻松地剪辑和组合视频素材。其次&#xff0c;该软件还支持多个轨…

【嵌入式-Linux】安装实时内核

原文链接&#xff1a;Docs 所有内容链接&#xff1a;博客学习目录_Howe_xixi的博客-CSDN博客 参考链接&#xff1a;Ubuntu 18.04安装 RT-PREEMPT 实时内核及补丁【过程记录】_fully preemptible kernel(rt)_黄小白的进阶之路的博客-CSDN博客 Ubuntu20.04编译Linux内核_zstd: n…

1024程序员节,一个ETL工程师的日常工作

“1024这个数字对程序员来说为什么很特别&#xff1f;” 这是因为 计算机硬件的运算进制大多是二进制 但是二进制太小了 所以聪明的程序猿就以2的10次幂来作为大多数程序的运算进制 而2⁰ 1024 例如&#xff1a;1G1024MB &#xff1b;1MB1024KB 1024 MB 1GB “一级棒…

静态划分信道(信道划分介质访问控制)

经过前面的总结和学习&#xff0c;我们再来总结一下信道划分介质访问控制。 一、传输数据的两种链路 首先&#xff0c;在数据链路层中我们有提到传输数据的两种链路&#xff1a;点对点式链路以及广播式链路。 点对点式链路&#xff1a; 两个相邻节点通过一个链路相连&#…

Qt扫描-QMoive 理论总结

QMoive 理论总结 一、概述二、使用1. 使用2. 信号发出时机 三、控制的相关槽函数四、信号 一、概述 QMovie类是一个使用QImageReader播放 动画 的方便类。这个类用于显示没有声音的简单动画&#xff0c;一般即是 gif 动画。如果要显示视频和媒体内容&#xff0c;请使用Qt Mult…

Flutter最全面试题大全

在理解这些问题之前&#xff0c;建议看一下Flutter架构原理&#xff0c;如下链接&#xff1a; https://blog.csdn.net/wang_yong_hui_1234/article/details/130427887?spm1001.2014.3001.5501 目录 一. 有个Text节点&#xff0c;由于文字内容过多&#xff0c;发生了溢出错误&…

thinkPHP5.0字符集修改为utf8mb4

Q:为什么要用utf8mb4 A:utf8mb4可以直接保存emoji字符 MySQL修改字符集 // 数据表编码改为utf8mb4 function to_utf8mb4() {$database config("database.database");$sql "ALTER DATABASE {$database} CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;&q…

服务端返回响应 任务分配给线程

package TCP6;// 完成TCP通信服务端 多发多收import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.conc…

I/O模型之非阻塞IO

简介 五种IO模型   阻塞IO   非阻塞IO   信号驱动IO   IO多路转接    异步IO 代码书写 非阻塞IO 再次理解IO 什么是IO&#xff1f;什么是高效的IO&#xff1f; 为了理解后面的一个问题&#xff0c;我们首先要再重新理解一下什么是IO 在之前的网络介绍中&#xff…