【算法】雪花算法生成分布式 ID

SueWakeup

                                                      个人中心:SueWakeup

                                                      系列专栏:学习Java框架

                                                      个性签名:人生乏味啊,我欲令之光怪陆离

本文封面由 凯楠📷 友情赞助播出!

目录

1. 什么是分布式 ID

2. 分布式 ID 基本要求

3. 数据库主键自增

4. UUID

5. Snowflake 雪花算法

5.1 开源的雪花算法

注:手机端浏览本文章可能会出现 “目录”无法有效展示的情况,请谅解,点击侧栏目录进行跳转   


1. 什么是分布式 ID

在理解分布式 ID 之前请先阅读:【概念】神马是分布式?

分布式 ID 是指在分布式系统中,数据库的自增 ID 不能满足需求,需要在不同的节点之间通过一个唯一 ID 来进行标识。

个人理解:在分布式微服务项目中,多个线程同时对一张表新增数据,且这张表的主键 ID 存在唯一性 


2. 分布式 ID 基本要求

基本要求描述
全局唯一在整个分布式系统中全局唯一,不能出现重复 ID
高性能高可用分布式 ID 的生成速度要快,生成分布式 ID 的服务要保证可用性无限接近于 100%
趋势递增在 MySQL InnoDB 引擎中使用的是聚焦索引,由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能
单调递增保证下一个 ID 一定大于上一个 ID
具体的业务含义生成的 ID 拥有具体的业务含义,可以让定位问题以及开发更透明化
独立部署在分布式系统单独有一个发号器服务,专门用来生成分布式 ID,生成的 ID 的服务和业务相关的服务解耦,但会带来服务之间网络调用消耗增加
信息安全ID 中不能包含敏感信息,如果 ID 是连续的,恶意用户的扒取工作就非常容易做,订单号就更危险了,竞争对手可以获取到我们一天的订单信息,所以一些应用场景下,ID 需要呈现无规则状态

3. 数据库主键自增

通过关系型数据库的主键自增的方式,产生唯一的 ID

优点缺点
  • 实现简单、ID 有序递增、存储空间消耗小
  • 单击模式下并发量不大,性能瓶颈限制在单台 MySQL 的读写性能
  • 数据库服务器不可用时,整个系统瘫痪
  • ID 没有具体业务含义
  • 安全问题
  • 每次获取 ID 都要访问数据库

解决方案:

         在分布式系统中多部署几台及其,每台机器设置不同的初始值,且步长和机器数相等

如:两台机器,设置步长 step 为 2, TicketServer1 的初始值为 1(1,3,5,7,9...)、TicketServer2 的初始值为 2(2,4,6,8,10...)


4. UUID

Universally Unique Identifier(通用唯一标识符)的缩写

UUID 包含 32 个 16 进制数字(8-4-4-4-12)

生成规则:包括 MAC 地址、时间戳、命名空间(Namespace)、随机或伪随机数、时序等元素,基于这些规则生成的 UUID 不会重复

UUID.randomUUID();
优点缺点
  • 性能非常高,本地生成,没有网络消耗
  • 不易于存储:16 字节 128 位,通常以长度为 36 的字符串表示,很多场景不适用
  • 信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露
  • 不满足 MySQL 主键要求:MySQL 官方有明确的建议主键要尽量越短越好
  • 对 MySQL 索引不利:作为数据库主键,在 InnoDB 引擎下,UUID 的无序性可能会引起数据位置频繁变动,影响性能

5. Snowflake 雪花算法

Snowflake 产生的 ID 由 64位 二进制数字组成,被拆分成 4 个部分:

  • 符号位:标识正负,始终为0
  • 时间戳:单位 ms(毫秒),可以支持 2^41 毫秒(约 69 年)
  • 工作时间 ID:一般前 5 位表示机房 ID,后 5 位表示机器ID,用于区分不同集群/机房的节点,10 位的长度,可以表示 1024 个不同节点。
  • 序列号:序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数,也就是说单台机器每毫秒最多可以生成 4096 个唯一ID,最大支持 400W 左右的并发量。

5.1 开源的雪花算法

public class SnowFlake {

    // 机房(数据中心)ID
    private long datacenterId;

    // 机器 ID
    private long workerId;

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

    // 开始时间戳
    private long twepoch = 1634393012000L;  // 时间起点,这里设置为"2021-10-17 00:00:00"

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

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

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

    // 最大数据中心ID:5 bit 最多只能有31个数字,就是说数据中心id最多只能是32以内
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);  // 最大数据中心ID值

    // 同一毫秒内的序列号位数:12 bit
    private long sequenceBits = 12L;

    // workerId左移位数:12
    private long workerIdShift = sequenceBits;

    // datacenterId左移位数:12+5
    private long datacenterIdShift = sequenceBits + workerIdBits;

    // timestamp左移位数:12+5+5
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    // 序列号掩码:4095 (0b111111111111=0xfff=4095)
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    // 上次时间戳
    private long lastTimestamp = -1L;

    // 构造函数,传入workerId和datacenterId
    public SnowFlake(long workerId, long datacenterId) {
        this(workerId, datacenterId, 0);
    }

    // 构造函数,传入workerId、datacenterId和sequence
    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;

            if (sequence == 0) {
                // 如果同一毫秒内的序列号超出范围,等待下一毫秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 不同毫秒内,序列号重置为0
            sequence = 0;
        }

        // 更新上次时间戳
        lastTimestamp = timestamp;

        // 生成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();
    }

    // 主函数,测试生成ID
    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());
        }
    }

}

测试用例

  SnowFlake flake1 = new SnowFlake(1, 12);
        SnowFlake flake2 = new SnowFlake(1, 12);

        Thread t1 = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("t1-"+flake1.nextId());
                }
            }
        };

        Thread t2 =new Thread(){
            @Override
            public void run(){
                for(int i=0;i<10;i++){
                    System.out.println("t2-"+flake2.nextId());
                }
            }
        };

        t1.start();
        t2.start();


        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Day74:WEB攻防-机制验证篇重定向发送响应状态码跳过步骤验证码回传枚举

目录 验证码突破-回传显示&规律爆破 某目标回显显示 某APP验证码爆破 验证目标-重定向用户&重定向发送 某CMS重定向用户 某CMS重定向发送 验证逻辑-修改响应包&跳过步骤URL 某APP修改响应包 某APP跳过步骤URL 实战SRC验证逻辑挖掘分享案例 短信验证码回…

01. Java 中的数据类型

数据类型 Java 是一门强语言&#xff0c;语言的数据类型分为&#xff1a;八种基本类型和三种引用类型(数组, class, interface)。在声明变量或常量时必须指定数据类型。 整数类型 Java 中整数类型都是有符号型。 整型分为int(默认), byte、short、int 和 long 四种类型&#…

Oracle19C图形界面安装教程

文章目录 一、安装前的准备1、安装Linux操作系统2、配置网络源或者本地源3、hosts文件配置 二、Oracle19c安装过程1、安装相关软件&#xff1a;2、用户与组&#xff1a;3、修改内核参数&#xff1a;4、资源限制&#xff1a;5、配置用户环境变量&#xff1a;6、创建相关文件目录…

NASA数据集——2017-2019年阿拉斯加和加拿大北极地区RGB 合成图像V2(L1/L2数据集)

简介 ABoVE: Hyperspectral Imagery AVIRIS-NG, Alaskan and Canadian Arctic, 2017-2019 V2 高光谱成像 AVIRIS-NG&#xff0c;阿拉斯加和加拿大北极地区&#xff0c;2017-2019 V2 摘要 本数据集提供了机载可见光/红外成像分光计-下一代&#xff08;AVIRIS-NG&#xff09;…

用Compute Shader处理图像数据后在安卓机上不能正常显示渲染纹理

1&#xff09;用Compute Shader处理图像数据后在安卓机上不能正常显示渲染纹理 2&#xff09;折叠屏适配问题 3&#xff09;Prefab对DLL中脚本的引用丢失 4&#xff09;如何优化Unity VolumeManager中的ReplaceData 这是第378篇UWA技术知识分享的推送&#xff0c;精选了UWA社区…

智慧公厕助力“厕所革命”,方便小事关乎文明大事

公共厕所是城市文明建设的重要组成部分&#xff0c;而智慧公厕则是厕所变革的一项全新举措。通过物联网、互联网、大数据、云计算、自动化控制技术的应用&#xff0c;智慧公厕实现了对公共厕所全方位的业务融合和智能化管理。下面将以智慧公厕源头实力厂家广州中期科技有限公司…

【视频图像取证篇】模糊图像增强技术之去噪声类滤波场景应用小结

【视频图像取证篇】模糊图像增强技术之去噪声类滤波场景应用小结 模糊图像增强技术之去噪声类滤波场景应用小结—【蘇小沐】 文章目录 【视频图像取证篇】模糊图像增强技术之去噪声类滤波场景应用小结&#xff08;一&#xff09;去噪声类滤波器1、去块滤波器&#xff08;Deblo…

【WSL】Ubuntu 20.04 字符集不认识中文,及其中文路径

1. 问题 $ locale locale: Cannot set LC_CTYPE to default locale: No such file or directory locale: Cannot set LC_ALL to default locale: No such file or directory LANGen_US.UTF-8 LANGUAGE LC_CTYPEUTF-8 LC_NUMERIC"en_US.UTF-8" LC_TIME"en_US.UT…

Flutter 3.13 之后如何监听 App 生命周期事件

在 Flutter 中&#xff0c;您可以监听多个生命周期事件来处理应用程序的不同状态&#xff0c;但今天我们将讨论 didChangeAppLifecycleState 事件。每当应用程序的生命周期状态发生变化时&#xff0c;就会触发此事件。可能的状态有 resumed 、 inactive 、 paused 、 detached …

idea 开发serlvet篮球秩序册管理系统idea开发mysql数据库web结构计算机java编程layUI框架开发

一、源码特点 idea开发 java servlet 篮球秩序册管理系统是一套完善的web设计系统mysql数据库 系统采用serlvetdaobean mvc 模式开发&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 servlet 篮…

PCL ICP配准高阶用法——统计每次迭代的配准误差并可视化

目录 一、概述二、代码实现三、可视化代码四、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 在进行论文写作时,需要做对比实验,来分析改进算法的性能,期间用到了迭代误差分布统计的比较分析,为直…

MySQL数据库基本操作(增删改查)与用户授权

前言 SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;是一种用于管理关系数据库系统的语言。SQL的设计目标是提供一种简单、直观的语言&#xff0c;使得用户可以通过编写SQL语句来处理他们想要的数据和操作。 目录 一、结构介绍 1. 查看信…

【C语言】文件操作揭秘:C语言中文件的顺序读写、随机读写、判断文件结束和文件缓冲区详细解析【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】文件操作揭秘&#xff1a;C语言中文件的顺序读写、随机读写、判断文件结束和文件缓冲区详细解析【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 欢迎来到本篇博客&…

Mybatis之自定义映射resultMap

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

Qualcomm AI Hub-示例(三)模型推理

文章介绍 Qualcomm AI Hub提供了部署在云端边缘物理设备执行模型推理的任务&#xff0c;让你能够快速的评估在真实硬件上模型推理的精度和性能。本文介绍了如何使用AI Hub提供的接口在云端设备执行推理&#xff0c;更多详情可以参阅 Running Inference 模型推理 出于功耗和性能…

Rust Rocket简单入门

简介 Rust中最知名的两个web框架要数Rocket和Actix了&#xff0c;Rocket更注重易用性&#xff0c;Actix则更注重性能。这里只是了解一下Rust下的WebAPI开发流程&#xff0c;就学一下最简单的 Rocket。 Rocket 是一个用于 Rust 的异步 Web 框架&#xff0c;专注于可用性、安全性…

FreeRTOS教程9 软件定时器

目录 1、准备材料 2、学习目标 3、前提知识 3.1、软件定时器回调函数 3.2、软件定时器属性和状态 3.2.1、周期 3.2.2、分类 3.2.3、状态 3.3、软件定时器运行原理 3.3.1、RTOS 守护进程任务 3.3.2、定时器命令队列 3.3.3、守护进程任务调度 3.4、创建、启动软件定…

Web框架开发-Django模型层(数据库操作)

一、ORM介绍 MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动ORM是“对象-关系-映…

ubuntu20.04安装Pycharm

下载pycharm安装包 https://www.jetbrains.com/pycharm/download/#sectionlinux 使用社区版点击download 下载好的pycharm如图所示&#xff0c;右键解压&#xff1a; 打开终端&#xff0c;输入cd命令&#xff0c;进入刚刚解压文件夹下的bin文件夹&#xff0c;命令行是cd 文…

手撕算法-二叉搜索树与双向链表

牛客BM30。 描述&#xff1a;https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId295&tqId23253&ru/exam/oj&qru/ta/format-top101/question-ranking&sourceUrl%2Fexam%2Foj分析&#xff1a;二叉搜索树的中序遍历是递增序列。可以利用…
最新文章