【分布式】分布式ID

news2024/11/24 8:25:17

目录

  • 前言
  • 一、雪花算法snowflake
    • 1. 组成
    • 2. 优缺点
    • 3. 时钟回拨怎么解决
      • a. 时钟回拨
      • b. 解决方案
    • 4. 项目中如何使用
  • 二、基于Redis
  • 三、基于Zookeeper
  • 四、号段模式
  • 五、指定步长的自增ID
  • 六、UUID
    • 参考
  • 六、扩展
  • 总结

前言

分布式场景下,一张表可能分散到多个数据结点上。因此需要一些分布式ID的解决方案。

分布式ID需要有几个特点:

  • 全局唯一(必要) :在多个库的主键放在一起也不会重复
  • 有序(必要) :避免频繁触发索引重建
  • 信息安全:ID连续,可以根据订单编号计算一天的单量,造成信息泄露
  • 包含时间戳:能够快速根据ID得知生成时间

下面几种方案按推荐顺序排序,越推荐使用越靠前。

一、雪花算法snowflake

64 位的 long 类型的唯一 id
在这里插入图片描述

1. 组成

1)1位不用

带符号整数第1位是符号位,正数是0,ID一般为正数,此位不用。

2)41位毫秒级时间戳

41位存储当前时间截 – 开始时间截得到的差值,可以表示 2 41 2^{41} 241个毫秒的值,转化成单位年则是: 2 41 1000 ∗ 60 ∗ 60 ∗ 24 ∗ 365 = 69 年 \frac{2^{41}}{1000∗60∗60∗24∗365}=69年 1000606024365241=69

注:开始时间截由程序指定,一般是id生成器开始使用的时间,设置好后避免更改。依赖服务器时间,服务器时钟回拨时可能会生成重复 id。

3)10位机器ID

生成ID的服务可以部署在1024台机器上

4)12位序列号
能够表示4096个序列号。

因此,某一毫秒,同一台机器,最多能生成4096个序号。理论上单机QPS最大为4096*1000=409.6w/s

2. 优缺点

优点:

  • ID不重复:用时间戳+机器+序号生成不重复ID
  • 性能高:在内存中生成
  • 有序

缺点:

依赖服务器时间,存在时钟回拨的问题。

3. 时钟回拨怎么解决

时钟回拨可能产生重复ID进而影响关联系统。

a. 时钟回拨

什么是时钟回拨: 服务器上的时间倒退回之前的时间

哪些情况造成时钟回拨:

  • 人为修改服务器时间
  • 时钟同步后,由于机器之间时间不同,可能产生时钟回拨

b. 解决方案

算法中会记录当前服务上次生成ID的最后时间,只需要保证我下次生成ID的时间大于上次最后时间即可。根据回拨后时间距离上次生成最后时间大小,可以有不同的解决方案。

  • 相差0~100ms :等待直至当前时间超过上次最后生活时间
  • 相差100ms~1s:采用等待方式可能导致接口超时。可以记录已生成ID的最大ID,在这个基础上++。(预留扩展位,在扩展位上增加。回拨后又回拨可能有问题)
  • 相差1s~5s:采用最大ID增加的方式,时间过长可能导致范围溢出。可以生成ID服务响应异常,由调用方例如基于Ribbon调用其他生成ID服务。
  • 相差超过5s:采用Ribbon循环调用的方式,下次访问到时钟回拨的服务可能还没达到上次生成最后时间,浪费时间。可以让超过5s的服务主动下线,并通知运维,人工介入,等待时钟正常后再重启。

4. 项目中如何使用

在这里插入图片描述

时钟回拨的处理逻辑在nextId()里的if (timestamp < lastTimestamp) 逻辑下。这里直接抛出异常。

public class SnowflakeIdWorker {

    // ==============================Fields===========================================
    /** 开始时间截 (2020-01-01) */
    private final long twepoch = 1577808000000L;

    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;

    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 支持的最大数据标识id,结果是31 */
    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=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~31) */
    private long workerId;

    /** 数据中心ID(0~31) */
    private long datacenterId;

    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;

    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;

    //==============================Constructors=====================================
    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    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;
    }

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    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 {
            sequence = 0L;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================
    /** 测试 */
    public static void main(String[] args) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);

        for (int i = 0; i < 100; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
    }
}

二、基于Redis

三、基于Zookeeper

四、号段模式

数据库中保存号段表,每次从数据库获取一个号段范围,由服务在内存中自增生成ID,直到达到号段范围再去获取。

id业务类型最大可用ID号段长度版本号
1xxx200010000
updateset 最大可用ID = 3000, version = version + 1 where version = 0 and biz_type = xxx

采用乐观锁的方式避免长时间锁表。

优点:

  • ID有序递增
  • 对数据库压力比较小

缺点:

  • 存在安全问题,用ID可以判断数据量
  • 存在单点问题,集群实现困难

五、指定步长的自增ID

多主集群模式下,表主键设置自增ID,多节点之间会有重复ID。需要采用指定初始值的自增步长
举例:两台数据库A,B。A初始值和步长为(1,2),B初始值和步长为(2,2)。两张表生成的主键分别为
A:1,3,5,7…
B:2,4,6,8…

设置方法:

set @@auto_increment_offset = 1;     -- 起始值
set @@auto_increment_increment = 2;  -- 步长

优点:

  • 简单、解决单点问题

缺点:

  • 扩容困难

六、UUID

128位长的字符标识串,由32个16进制数字组成,用-连接,共36个字符。如:03425604-5462-11ee-80ad-80fa5b8732b1

生成算法与重复性:

  • 基于随机数:不重复
  • 基于MAC地址:不重复
  • 基于时间戳:可能重复

作为分布式ID的优缺点:

  • 优点:本地生成,性能高,无网络损耗
  • 缺点:
    • 无序:造成索引重建,入库性能差
    • 字符串长:需要36字符

参考

  • UUID会重复吗?
  • 雪花算法视频
  • 9种分布式ID生成方式

六、扩展

  • 百度:UidGenerator
  • 美团:Leaf

总结

优点缺点
uuid实现简单连续性差,作为主键每次新增数据都会触发索引重建。
分布式环境中可能重复
雪花算法性能好,有序依赖服务器时间,时钟回拨可能生成重复ID
号段模式
redis/zookeeperRedis基于INCR 命令生成 分布式全局唯一id
zookeeper一种通过节点,一种通过节点的版本号

基因算法

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

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

相关文章

【数据结构】C++实现二叉搜索树

二叉搜索树的概念 二叉搜索树又称为二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树&#xff1a; 若它的左子树不为空&#xff0c;则左子树上所有结点的值都小于根结点的值。若它的右子树不为空&#xff0c;则右子树上所有结点的值都大于根结…

问道管理:注册制对涨幅的限制?

注册制作为中国证券商场变革的重中之重&#xff0c;其本质是将发行商场转移到审阅商场&#xff0c;实现商场准入的有效管控。与此同时&#xff0c;注册制关于股票商场的涨幅也有必定的约束。 首先&#xff0c;注册制能够经过严厉的审阅来制约公司发行股票的数量和节奏。传统IP…

Linux 多线程( 进程VS线程 | 线程控制 )

文章目录 Linux进程 VS 线程进程的多个线程共享 进程和线程的关系线程创建 pthread_create获取线程ID pthread_self线程等待 pthread_join终止线程进程分离线程ID及进程地址空间布局 Linux进程 VS 线程 进程是资源分配的基本单位。线程是OS调度的基本单位。 线程共享进程数据…

Michael.W基于Foundry精读Openzeppelin第34期——MerkleProof.sol

Michael.W基于Foundry精读Openzeppelin第34期——MerkleProof.sol 0. 版本0.1 MerkleProof.sol 1. 目标合约2. 代码精读2.1 processProof(bytes32[] memory proof, bytes32 leaf) && processProofCalldata(bytes32[] calldata proof, bytes32 leaf)2.2 verify(bytes32[…

图像处理之频域滤波DFT

摘要&#xff1a;傅里叶变换可以将任何满足相应数学条件的信号转换为不同系数的简单正弦和余弦函数的和。图像信号也是一种信号&#xff0c;只不过是二维离散信号&#xff0c;通过傅里叶变换对图像进行变换可以图像存空域转换为频域进行更多的处理。本文主要简要描述傅里叶变换…

Heap及其应用

目录 堆的相关知识 什么是堆&#xff1f; 堆的性质&#xff1a; 堆的实现&#xff1a; 堆的结构&#xff1a; &#xff08;一&#xff09;堆的插入 向上调整法&#xff1a; 寻找父节点 循环结束条件 代码&#xff1a; &#xff08;二&#xff09;堆的删除 删除根节点…

Huggingface:免费开源AI人工智能API工具平台

| 【产品介绍】 • 名称 Huggingface • 成立/上线时间 2016年 • 具体描述 HuggingFace是一个开源的自然语言处理AI工具平台&#xff0c;它为NLP的开发者和研究者提供了一个简单、快速、高效、可靠的解决方案&#xff0c;让NLP变得更加简…

R绘制箱线图

代码大部分来自boxplot()函数的帮助文件&#xff0c;可以通过阅读帮助文件&#xff0c;调整代码中相应参数看下效果&#xff0c;进而可以理解相应的作用&#xff0c;帮助快速掌握barplot()函数的用法。 语法 Usage(来自帮助文件) barplot(height, ...)## Default S3 method: …

lua环境搭建数据类型

lua作为一门计算机语言&#xff0c;从语法角度个人感觉还是挺简洁的接下来我们从0开始学习lua语言。 1.首先我们需要下载lua开发工具包 在这里我们使用的工具是luadist 下载链接为&#xff1a;https://luadist.org/repository/下载后的压缩包解压后就能用。 2.接下来就是老生…

听GPT 讲Istio源代码--istioctl

在 Istio 项目的 istioctl 目录中&#xff0c;有一些子目录&#xff0c;每个目录都有不同的作用和功能。以下是这些子目录的详细介绍&#xff1a; /pkg: pkg 目录包含了 istioctl 工具的核心代码和库。这些代码和库提供了与 Istio 控制平面交互的功能&#xff0c;例如获取和修改…

postgresql 内核源码分析 btree索引插入分析,索引页面分裂流程,多举措进行并发优化,对异常进行保护处理

Btree索引插入流程分析 ​专栏内容&#xff1a; postgresql内核源码分析手写数据库toadb并发编程 ​开源贡献&#xff1a; toadb开源库 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&a…

Swing程序设计详解(一)

【今日】 “若你决定灿烂&#xff0c;山无遮&#xff0c;海无拦” 目录 初识Swing 一 Swing简述 二 Swing常用窗体 2.1 JFrame窗体 2.2 JDialog对话框 2.3JOptionPane小型对话框 (1)通知框 (2)确认框 (3)输入框 (4)自定义对话框 三 常用布局管理器 3.1 绝…

JWT生成与解析/JWT令牌前端存储

第一步&#xff1a;创建项目 添加Maven依赖&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version> </dependency> <dependency><groupId>org.s…

【C++】深拷贝和浅拷贝 ② ( 默认拷贝构造函数是浅拷贝 | 代码示例 - 浅拷贝造成的问题 )

文章目录 一、默认拷贝构造函数是浅拷贝1、默认拷贝构造函数2、默认拷贝构造函数是浅拷贝机制 二、代码示例 - 浅拷贝造成的问题 一、默认拷贝构造函数是浅拷贝 1、默认拷贝构造函数 如果 C 类中 没有定义拷贝构造函数 , C 编译器会自动为该类提供一个 " 默认的拷贝构造函…

GeoJSON转STL:地形3D打印

我们通过将 GeoJSON 形状坐标提取到点云中并使用 Open3d 应用泊松重建&#xff0c;从 GeoJSON 数据重建 STL 网格。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 我对打印 GeoJSON 山丘的第一次尝试深感不满&#xff0c;因此想出了一个三步流程&#xff0c;仅使用开源…

Acwing 828. 模拟栈

Acwing 828. 模拟栈 题目要求思路讲解代码展示 题目要求 思路讲解 栈&#xff1a;先进后出 队列&#xff1a;先进先出 代码展示 #include <iostream>using namespace std;const int N 100010;int m; int stk[N], tt;int main() {cin >> m;while (m -- ){string o…

【JVM】经典垃圾收集器

文章目录 说明新生代收集器Serial收集器ParNew收集器Parallel Scavenge收集器 老年代收集器Serial Old收集器Parallel Old收集器CMS收集器 Garbage First收集器需要解决的问题运作过程CMS和G1的区别 说明 Java中有许多垃圾收集器&#xff08;Garbage Collector&#xff0c;GC&…

Spring Cloud Alibaba系列之nacos:(5)源码本地环境搭建

传送门 Spring Cloud Alibaba系列之nacos&#xff1a;(1)安装 Spring Cloud Alibaba系列之nacos&#xff1a;(2)单机模式支持mysql Spring Cloud Alibaba系列之nacos&#xff1a;(3)服务注册发现 Spring Cloud Alibaba系列之nacos&#xff1a;(4)配置管理 为什么要搭建本地…

范文展示,如何三步写出一篇满意的论文

第一步&#xff1a;输入文章关键信息 文章标题&#xff0c;写论文的话即为拟定的论文标题&#xff0c;例如这篇范文中的题目为“阳明心学研究” 关键词&#xff0c;可以写出多个论文主题相关的关键词&#xff0c;用逗号分开&#xff0c;例如这篇范文中只写了一个关键词“王阳…

CentOS 7.6使用mysql-8.0.31-1.el7.x86_64.rpm-bundle.tar安装Mysql 8.0

https://downloads.mysql.com/archives/community/是社区版的官网&#xff0c;可以选择版本下载。 cat /etc/redhat-release可以看到系统版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到版本是3.10.0-957.el7.x86_64。 yum remove -y mysql-libs把…