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

news2024/9/19 10:43:30
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.coloradmin.cn/o/1532983.html

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

相关文章

PyTorch 深度学习(GPT 重译)(四)

第二部分&#xff1a;从现实世界的图像中学习&#xff1a;肺癌的早期检测 第 2 部分的结构与第 1 部分不同&#xff1b;它几乎是一本书中的一本书。我们将以几章的篇幅深入探讨一个单一用例&#xff0c;从第 1 部分学到的基本构建模块开始&#xff0c;构建一个比我们迄今为止看…

【Python + Django】ORM 数据库操作

前言&#xff1a; 虽然我们知道了用MySQL数据库 pymysql可以进行数据库的连接&#xff0c; 但这样的方式太繁琐了。 本文介绍一下Django为我们提供的更简单便捷的数据库连接方式&#xff1a;ORM框架。 ORM为我们翻译代码&#xff0c;使得我们的代码更加简洁易懂。 1 连接…

28-5 文件上传漏洞 - 图片马

一、文件内容检测 解析漏洞定义 控制文件是否被当做后端脚本处理 二、图片马绕过 图片马;在图片中包含一句话木马。利用解析漏洞如.htaccess 或文件包含漏洞,对图片马进行解析,执行其中的恶意代码。优势在于可以绕过多种防护机制。 三、图片马制作方法: # 一句话马示例…

Maven Deploy测试

文章目录 Maven环境deployreleaseRepo Manager演示 RefFAQ Maven 环境 jdk8maven v3.9.5 deploy mvn install将jar存到localRepository&#xff0c;mvn deploy把jar推送到远程仓库&#xff0c;然后可以像central库那样下载依赖。 release 基于git执行项目发版流程&#x…

【机器学习智能硬件开发全解】(六)—— 政安晨:通过ARM-Linux掌握基本技能【认知准备:体系结构与汇编指令】

ARM-Linux体系是指基于ARM架构的Linux操作系统体系&#xff0c;其它常见的体系还有x86-Linux体系等。ARM架构是一种常用于移动设备和嵌入式系统的处理器架构&#xff0c;如手机、平板电脑、智能手表等都广泛使用ARM处理器。 ARM-Linux体系基于Linux开源操作系统&#xff0c;并…

Python将字符串转换为datetime

有这样一些字符串&#xff1a; 1710903685 20240320110125 2024-03-20 11:01:25 要转换成Python的datetime 代码如下&#xff1a; import functools import re from datetime import datetime, timedelta from typing import Union# pip install python-dateutil from date…

51单片机学习笔记8 中断系统及定时器

51单片机学习笔记8 中断系统及定时器 一、中断的概念二、51单片机的中断1. 51单片机的中断源2. 中断的优先级3. 中断结构4. 外部中断解读5. 定时器中断6. 串口中断 三、中断相关寄存器1. IE 中断允许寄存器2. TCON 中断请求标志3. IP 中断优先级 四、中断号五、代码实现按键 &a…

如何实现跨标签页通讯

什么是跨标签页通讯 同一浏览器&#xff0c;可以打开多个标签页&#xff0c;跨标签页通讯就是&#xff0c;一个标签页能够发消息给另一标签页。 有哪些实现方案 localStorage &#xff08;window.onstorage事件监听&#xff09;BroadcastChannel&#xff08;广播&#xff09…

Redis如何设置键的生存时间或过期时间

键的生存时间或过期时间 概述。 通过EXPIRE命令或者PEXIPIRE命令&#xff0c;客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间(Time To Live,TTL)&#xff0c;在经过指定的秒数或者毫秒数之后&#xff0c;服务器就会自动删除生存时间为0的键: 127.0.0.1:6379>…

Python零基础---爬虫技术相关

python 爬虫技术&#xff0c;关于数据相关的拆解&#xff1a; 1.对页面结构的拆解 2.数据包的分析&#xff08;是否加密了参数&#xff09;&#xff08;Md5 aes&#xff09;难易程度&#xff0c;价格 3.对接客户(433,334) # 数据库 CSV 4.结单&#xff08;发一部分数据&a…

酷开系统满足你的需求,加入酷开会员开启娱乐之旅

酷开科技深知家庭娱乐在我们生活中的重要性&#xff0c;因此&#xff0c;酷开科技不断努力为我们带来更好的内容和服务&#xff0c;在这里&#xff0c;我们能够享受到家庭娱乐的乐趣和便利&#xff0c;感受到酷开科技带来的温暖。电影迷、游戏迷还是音乐爱好者&#xff0c;酷开…

1236 - 二分查找

代码 #include<bits/stdc.h> using namespace std; int a[1100000]; int main() {int n,x,l,r,p,mid,i;cin>>n;for(i1;i<n;i)cin>>a[i];cin>>x;l1;rn;p-1;while(l<r){mid(rl)/2;if(a[mid]x){pmid;break;}else if(x<a[mid]) rmid-1;else if(x…

k8s为什么删除了pod但是还是没删除掉的问题,deployment在影响

deployment 影响pod删除 一、问题所在二、解决问题 一、问题所在 执行&#xff1a;kubectl get pods --all-namespaces&#xff0c;获取dashboard相关的pod kubectl get pods --all-namespaces | grep dashboardkubectl delete pod dashboard-metrics-scraper-546d6779cb-4x6…

备战秋招(coding篇)

其中coding题目来源于师兄面试经验 1、链表的结构体反转链表 本质上就是一个构造函数 struct ListNode{int val_;ListNode* next_;ListNode() : val_(0), next_(NULL) {}ListNode(int x) : val_(x), next_(NULL) {}ListNode(int x, ListNode* next) : val_(x), next_(next) …

【Spring Cloud】微服务通信概述

SueWakeup 个人主页&#xff1a;SueWakeup 系列专栏&#xff1a;学习技术栈 个性签名&#xff1a;人生乏味啊&#xff0c;我欲令之光怪陆离 本文封面由 凯楠&#x1f4f7; 友情赞助播出 目录 前言 1. Dubbo&#xff08;Spring Cloud Alibaba&#xff09;和 Spring Cloud 的适…

使用ollama + webui 运行任意大模型

安装ollama https://hub.docker.com/r/ollama/ollama docker run -d -v ~/Documents/work/softs/docker/ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama验证安装 # 进入容器docker exec -it ollama bash # 运行大模型 ollama run llama2 # 发送请求&…

【python + Django】Django模板语法 + 请求和响应

前言&#xff1a; 现在现在&#xff0c;我们要开始将变量的值展现在页面上面啦&#xff01; 要是只会显示静态页面&#xff0c;我们的页面也太难看和死板了&#xff0c; 并且数据库的数据也没法展现在页面上。 但是呢&#xff0c;模板语法学习之后就可以啦&#xff01;&…

笔记本8代i5和台式机12代i5的性能比较

一、 台式机12代i5 二、笔记本8代i5 在多核性能上差不多是2.4倍&#xff0c;所以跑大一点的Matlab或者别的程序&#xff0c;用台式机&#xff0c;后边实验室能用上超多核服务器另说。

【Flutter】文件选择器(file_picker)的用法

Flutter 没有提供内置的文件选择器&#xff0c;但社区内有人贡献了一个比较完整的解决方案——file_picker。 file_picker 的 API 简洁易用&#xff0c;支持全平台&#xff08;Android / iOS / Mac / Linux / Windows&#xff09;&#xff0c;是我开发桌面应用时的首选。 这边…

IDEA中快速配置Git

Git介绍&#xff1a; Git下载 idea中配置Git