深入探讨分布式ID生成方案

🎈🎈作者主页: 喔的嘛呀🎈🎈
🎈🎈所属专栏:python爬虫学习🎈🎈
✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨ 

目录

引言

一. UUID(Universally Unique Identifier)

二、数据库自增ID

三. 基于Redis的方案

四. Twitter的snowflake算法

五、百度UidGenerator

结语


引言

在分布式系统中,生成唯一标识符(ID)是一个常见的需求。在这篇博客中,我们将介绍几种常见的分布式ID生成方案,包括UUID、Snowflake算法、基于数据库的方案和基于Redis的方案。我们将深入探讨每种方案的原理、优缺点,并提供相应的代码示例。

一. UUID(Universally Unique Identifier)

UUID(Universally Unique Identifier)是一种标准化的128位数字(16字节)格式,通常用32个十六进制数字表示。UUID的目的是让分布式系统中的多个节点生成的标识符在时间和空间上都是唯一的。

UUID通常由以下几部分组成:

  1. 时间戳:占据前32位,表示生成UUID的时间戳。
  2. 时钟序列号:占据接下来的16位,保证在同一时刻生成的UUID的唯一性。
  3. 全局唯一的节点标识符:占据最后的48位,通常是机器的MAC地址。

UUID的生成方法有多种,其中比较常见的是基于当前时间戳和随机数生成。Java中可以使用java.util.UUID类来生成UUID,示例如下:

import java.util.UUID;

public class UUIDGenerator {
    public static void main(String[] args) {
        UUID uuid = UUID.randomUUID();
        System.out.println("Generated UUID: " + uuid.toString());
    }
}

这段代码将生成一个类似于550e8400-e29b-41d4-a716-446655440000的UUID。由于UUID的唯一性和随机性,通常用于分布式系统中的唯一标识符,例如作为数据库表的主键。 

二、数据库自增ID


使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同
步长,生成不重复ID的策略来实现高可用。
优点:数据库生成的ID绝对有序,高可用实现方式简单
缺点:需要独立部署数据库实例,成本高,有性能瓶颈

在许多关系型数据库中,自增ID是一种常见的用于唯一标识表中记录的方式。下面我将以MySQL为例,介绍如何在数据库中使用自增ID。

首先,我们需要创建一个带有自增ID的表。以下是一个简单的示例表的创建语句:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL
);

在这个例子中,id 列被定义为自增列,并且被指定为主键。每次向表中插入一条记录时,id 列都会自动递增,确保每个记录都有唯一的ID。

接下来,我们可以通过插入数据来演示自增ID的工作原理:

INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');

查询表中的数据:

SELECT * FROM users;

输出应该类似于:

+----+---------+------------------+
| id | name    | email            |
+----+---------+------------------+
| 1  | Alice   | alice@example.com|
| 2  | Bob     | bob@example.com  |
| 3  | Charlie | charlie@example.com|
+----+---------+------------------+

每次插入一条记录时,id 列都会自动递增。这就是自增ID的基本工作原理。

三. 基于Redis的方案

Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保
证生成的 ID 肯定是唯一有序的。
优点:不依赖于数据库,灵活方便,且性能优于数据库;数字ID天然排序,对分页或者需要排
序的结果很有帮助。
缺点:如果系统中没有Redis,还需要引入新的组件,增加系统复杂度;需要编码和配置的工作
量比较大。
考虑到单节点的性能瓶颈,可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台
Redis。可以初始化每台 Redis 的值分别是1, 2, 3, 4, 5,然后步长都是 5。

在 Redis 中生成自增 ID 通常可以通过使用 INCR 命令实现。INCR 命令会将存储在指定键中的数字递增 1,并返回递增后的值。你可以利用这个特性来实现一个简单的自增 ID 生成器。以下是一个基本的示例:

import redis.clients.jedis.Jedis;

public class RedisIdGenerator {
    private Jedis jedis;

    public RedisIdGenerator() {
        this.jedis = new Jedis("localhost");
    }

    public long getNextId(String key) {
        return jedis.incr(key);
    }

    public static void main(String[] args) {
        RedisIdGenerator idGenerator = new RedisIdGenerator();
        String key = "my_id_counter";

        // 使用示例
        for (int i = 0; i < 5; i++) {
            long id = idGenerator.getNextId(key);
            System.out.println("Generated ID: " + id);
        }
    }
}

在这个示例中,我们首先创建了一个 RedisIdGenerator 类,该类包含一个 getNextId 方法,用于生成下一个自增 ID。在 main 方法中,我们创建了一个实例,并连续调用 getNextId 方法来生成 ID。

需要注意的是,这只是一个简单的示例。在实际应用中,你可能需要考虑并发访问时的线程安全性,以及如何处理 Redis 连接的创建和关闭等问题。

四. Twitter的snowflake算法

Twitter的Snowflake算法是一种用于生成分布式唯一ID的算法,它可以在分布式系统中生成全局唯一的ID。Snowflake算法的核心思想是将一个64位的long型的ID分成多个部分,包括时间戳、机器ID和序列号。具体来说,Snowflake算法的ID结构如下:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused |   timestamp   |   worker ID  | sequence
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 位表示未使用的位,可根据需要保留或用于其他用途。
  • 41位表示时间戳,可以表示的时间范围为2^41 / 1000 / 60 / 60 / 24 = 69年左右。
  • 10位表示机器ID,可以用来区分不同的机器。
  • 12位表示序列号,可以用来区分同一机器同一时间戳内生成的不同ID。

Snowflake算法生成ID的过程如下:

  1. 获取当前时间戳,单位是毫秒。
  2. 使用配置的机器ID。
  3. 如果当前时间戳与上一次生成ID的时间戳相同,则使用序列号加1;否则序列号重置为0。
  4. 将时间戳、机器ID和序列号合并生成最终的ID。

Snowflake算法的优点是生成的ID是递增的、趋势递增的,并且可以根据需要提取出生成ID的时间戳和机器ID。然而,Snowflake算法也有一些缺点,例如在高并发情况下可能会出现ID重复的情况,需要适当的措施来避免这种情况的发生。

Snowflake 算法是 Twitter 开源的一种分布式唯一 ID 生成算法,用于生成全局唯一的 ID。它的核心思想是将 ID 分为不同的部分,包括时间戳、机器 ID 和序列号。下面是一个详细的实现:

public class SnowflakeIdGenerator {
    private final long twepoch = 1288834974657L; // 起始时间戳,可以根据实际需求调整
    private final long workerIdBits = 5L; // 机器 ID 的位数
    private final long datacenterIdBits = 5L; // 数据中心 ID 的位数
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 最大机器 ID
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 最大数据中心 ID
    private final long sequenceBits = 12L; // 序列号的位数
    private final long workerIdShift = sequenceBits; // 机器 ID 左移位数
    private final long datacenterIdShift = sequenceBits + workerIdBits; // 数据中心 ID 左移位数
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 时间戳左移位数
    private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 序列号掩码

    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("Worker ID 必须介于 0 和 " + maxWorkerId + " 之间");
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException("Datacenter ID 必须介于 0 和 " + maxDatacenterId + " 之间");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨发生在 " + (lastTimestamp - timestamp) + " 毫秒内");
        }

        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        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) {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);

        // 使用示例
        for (int i = 0; i < 5; i++) {
            long id = idGenerator.nextId();
            System.out.println("Generated ID: " + id);
        }
    }
}

在这个实现中,我们首先定义了 Snowflake 算法中需要用到的各种参数和位移操作。然后,我们实现了一个 nextId 方法来生成下一个 ID。在 main 方法中,我们创建了一个 SnowflakeIdGenerator 实例,并连续调用 nextId 方法来生成 ID。

需要注意的是,Snowflake 算法中的时间戳部分可以根据实际需求进行调整,以确保生成的 ID 在不同时间内仍然是唯一的。

五、百度UidGenerator

百度的 UIDGenerator 是一个分布式唯一 ID 生成器,类似于 Twitter 的 Snowflake 算法,但在细节上有所不同。以下是一个简化的实现,展示了其基本原理:

import java.util.concurrent.atomic.AtomicLong;

public class BaiduUidGenerator {
    private final long twepoch = 1288834974657L; // 起始时间戳,可以根据实际需求调整
    private final long workerIdBits = 10L; // 机器 ID 的位数
    private final long sequenceBits = 12L; // 序列号的位数

    private final long workerIdShift = sequenceBits; // 机器 ID 左移位数
    private final long timestampLeftShift = sequenceBits + workerIdBits; // 时间戳左移位数
    private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 序列号掩码

    private final long workerId;
    private volatile long lastTimestamp = -1L;
    private volatile long sequence = 0L;

    public BaiduUidGenerator(long workerId) {
        if (workerId < 0 || workerId >= (1 << workerIdBits)) {
            throw new IllegalArgumentException("Worker ID 必须介于 0 和 " + ((1 << workerIdBits) - 1) + " 之间");
        }
        this.workerId = workerId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨发生在 " + (lastTimestamp - timestamp) + " 毫秒内");
        }

        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift)
                | (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) {
        BaiduUidGenerator uidGenerator = new BaiduUidGenerator(1);

        // 使用示例
        for (int i = 0; i < 5; i++) {
            long id = uidGenerator.nextId();
            System.out.println("Generated ID: " + id);
        }
    }
}

在这个实现中,我们首先定义了 BaiduUidGenerator 类,其中包含了与 Snowflake 算法类似的参数和位移操作。然后,我们实现了一个 nextId 方法来生成下一个 ID。在 main 方法中,我们创建了一个 BaiduUidGenerator 实例,并连续调用 nextId 方法来生成 ID。

需要注意的是,这只是一个简化的实现,实际应用中可能需要根据具体需求进行调整和优化。

结语

以上是几种常见的分布式ID生成方案,每种方案都有其适用的场景,开发人员可以根据实际需求选择合适的方案。

 

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

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

相关文章

【Functional Affordances】如何确认可抓取的区域?(前传)

文章目录 1. 【Meta AI】Emerging Properties in Self-Supervised Vision Transformers2. 【Meta AI】DINOv2: Learning Robust Visual Features without Supervision3. 【NeurIPS 2023】Diffusion Hyperfeatures: Searching Through Time and Space for Semantic Corresponden…

网安学习笔记-day9,DNS服务器介绍

文章目录 DNS服务器部署域名介绍及分类DNS解析解析过程1.递归查询2.迭代查询 DNS服务器部署准备阶段安装DNS服务 部署过程在另一台虚拟机查看是否能解析到baidu.com的地址测试解析 转发器 扩展命令 DNS服务器部署 DNS(Domain Name System) 域名介绍及分类 常用的www.baidu.c…

【unity】如何汉化unity编译器

在【unity】如何汉化unity Hub这篇文章中&#xff0c;我们已经完成了unity Hub的汉化&#xff0c;现在让我们对unity Hub安装的编译器也进行下汉化处理。 第一步&#xff1a;在unity Hub软件左侧栏目中点击安装&#xff0c;选择需要汉化的编译器&#xff0c;再点击设置图片按钮…

stitcher类实现多图自动拼接

效果展示 第一组&#xff1a; 第二组&#xff1a; 第三组&#xff1a; 第四组&#xff1a; 运行代码 import os import sys import cv2 import numpy as npdef Stitch(imgs,savePath): stitcher cv2.Stitcher.create(cv2.Stitcher_PANORAMA)(result, pano) stitcher.st…

自动化面试常见算法题!

1、实现一个数字的反转&#xff0c;比如输入12345&#xff0c;输出54321 num 12345 num_str str(num) reversed_num_str num_str[::-1] reversed_num int(reversed_num_str) print(reversed_num) # 输出 54321代码解析&#xff1a;首先将输入的数字转换为字符串&#xff…

ARMday7作业

实现三个按键的中断&#xff0c;现象和代码 do_ipr.c #include "stm32mp1xx_gic.h" #include "stm32mp1xx_exti.h" extern void printf(const char *fmt, ...); unsigned int i 0; void do_irq(void) {//获取要处理的中断的中断号unsigned int irqnoGI…

离线数仓(八)【DWD 层开发】

前言 1、DWD 层开发 DWD层设计要点&#xff1a; &#xff08;1&#xff09;DWD层的设计依据是维度建模理论&#xff08;主体是事务型事实表&#xff08;选择业务过程 -> 声明粒度 -> 确定维度 -> 确定事实&#xff09;&#xff0c;另外两种周期型快照事实表和累积型…

信号处理--情绪分类数据集DEAP预处理(python版)

关于 DEAP数据集是一个常用的情绪分类公共数据&#xff0c;在日常研究中经常被使用到。如何合理地预处理DEAP数据集&#xff0c;对于后端任务的成功与否&#xff0c;非常重要。本文主要介绍DEAP数据集的预处理流程。 工具 图片来源&#xff1a;DEAP: A Dataset for Emotion A…

如何备考2025年AMC8竞赛?吃透2000-2024年600道真题(免费送题

最近有家长朋友问我&#xff0c;现在有哪些类似于奥数的比赛可以参加&#xff1f;我的建议可以关注下AMC8的竞赛&#xff0c;类似于国内的奥数&#xff0c;但是其难度要比国内的奥数低一些&#xff0c;而且比赛门槛更低&#xff0c;考试也更方便。比赛的题目尤其是应用题比较有…

肿瘤靶向肽 iRGD peptide环肽 1392278-76-0 c(CRGDKGPDC)

RGD环肽 c(CRGDKGPDC)&#xff0c;iRGD peptide 1392278-76-0 结 构 式&#xff1a; H2N-CRGDKGPDC-OH(Disulfide Bridge:C1-C9) H2N-Cys-Arg-Gly-Asp-Lys-Gly-Pro-Asp-Cys-COOH(Disulfide Bridge:Cys1-Cys9) 氨基酸个数&#xff1a; 9 C35H57N13O14S2 平均分子量:…

聊聊低代码产品的应用场景

随着数字化转型的不断深入&#xff0c;企业对于快速开发和迭代软件应用的需求也越来越迫切。而在这样的背景下&#xff0c;低代码产品应运而生&#xff0c;成为了一种热门的技术解决方案。本文将解读低代码产品的定义并探讨其应用场景。 一、低代码产品的定义 低代码产品是一种…

企业计算机服务器中了rmallox勒索病毒怎么办,rmallox勒索病毒解密流程步骤

在网络技术飞速发展的时代&#xff0c;越来越多的企业离不开网络办公&#xff0c;通过网络开展各项工作业务成为企业的常态&#xff0c;这也被国外众多黑客组织盯上的原因&#xff0c;近期&#xff0c;网络勒索病毒攻击的事件频发&#xff0c;越来越多的企业开始重视企业数据安…

Rust语言中Regex正则表达式,匹配和查找替换等

官方仓库&#xff1a;https://crates.io/crates/regex 文档地址&#xff1a;regex - Rust github仓库地址&#xff1a;GitHub - rust-lang/regex: An implementation of regular expressions for Rust. This implementation uses finite automata and guarantees linear tim…

leetcode:2138. 将字符串拆分为若干长度为 k 的组(python3解法)

难度&#xff1a;简单 字符串 s 可以按下述步骤划分为若干长度为 k 的组&#xff1a; 第一组由字符串中的前 k 个字符组成&#xff0c;第二组由接下来的 k 个字符串组成&#xff0c;依此类推。每个字符都能够成为 某一个 组的一部分。对于最后一组&#xff0c;如果字符串剩下的…

【SAP2000】在框架结构中应用分布式面板荷载Applying Distributed Panel Loads to Frame Structures

在框架结构中应用分布式面板荷载 Applying Distributed Panel Loads to Frame Structures 使用"Uniform to Frame"选项,可以简单地将荷载用于更多样化的情况。 With the “Uniform to Frame” option, loads can be easily used for a greater diversity of situat…

如何高效阅读嵌入式代码

大家好&#xff0c;今天给大家介绍如何高效阅读嵌入式代码&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 高效阅读嵌入式代码需要一些技巧和实践经验。以下是一些建议&#xff…

Sublime 彻底解决中文乱码

1. 按ctrl&#xff0c;打开Console&#xff0c;输入如下代码&#xff1a; import urllib.request,os; pf Package Control.sublime-package; ipp sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHand…

excel统计分析——单向分组资料的协方差分析

参考资料&#xff1a;生物统计学 单向分组资料是具有一个协变量的单因素方差分析资料。 操作案例如下&#xff1a; 1、对x和y进行方差分析 由方差分析表可知&#xff1a;4种处理间&#xff0c;x存在显著差异&#xff0c;而y在处理间差异不显著。需要进行协方差分析&#xff0c…

【C语言】linux内核pci_iomap

一、pci_iomap /** pci_iomap 是一个用于映射 PCI 设备的 BAR&#xff08;Base Address Register&#xff0c;基地址寄存器&#xff09;的函数。* 此函数返回指向内存映射 IO 的指针&#xff0c;用于直接访问 PCI 设备的内存或 I/O 空间。* * 参数:* dev - 指向pci_dev结构的指…

IP如何异地共享文件?

【天联】 组网由于操作简单、跨平台应用、无网络要求、独创的安全加速方案等原因&#xff0c;被几十万用户广泛应用&#xff0c;解决了各行业客户的远程连接需求。采用穿透技术&#xff0c;简单易用&#xff0c;不需要在硬件设备中端口映射即可实现远程访问。 异地共享文件 在…