分布式ID详解

文章目录

    • 分布式ID
      • UUID
      • Snowflake算法
      • 数据库自增ID
      • Leaf算法
      • Redis自增ID

分布式ID

在我们业务数据量不大的时候,单库单表完全可以支撑现有业务,数据再大一点搞个MySQL主从同步读写分离也能对付。但随着数据日渐增长,主从同步也扛不住了,就需要对数据库进行分库分表。在分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了,这个时候就需要生成分布式ID了。

在分布式系统中,分布式ID的使用场景主要包括高并发请求、跨服务的数据一致性和多节点的唯一标识符生成。例如,在电商平台中,为每个订单生成唯一的ID是非常重要的。一个高并发的电商系统可能有多个服务器同时处理订单请求,使用分布式ID可以避免ID冲突。类似地,在微服务架构中,各个服务可能需要生成唯一的标识符来标记请求或数据记录。分布式ID使得这些标识符在整个系统中保持唯一,从而避免数据混乱或冲突。

分布式ID生成一般都需要满足这些条件:

  • 全局唯一:必须保证ID是全局性唯一的,这是基本要求;
  • 高性能:高可用低延时,ID生成响应要块,否则反倒会成为业务瓶颈;
  • 高可用:100%的可用性是骗人的,但是也要无限接近于100%的可用性;
  • 趋势递增:如果要把ID存放在数据库的话,ID的有序性可以提升数据库写入速度。并且很多时候,我们还很有可能会直接通过ID来进行排序;
  • 安全:分布式ID中不包含敏感信息;

有以下常见几种实现分布式ID方式:

实现方式描述优点缺点实际使用场景
UUID生成128位长的唯一标识符,通常表示为32个十六进制字符。实现简单,生成速度快。ID较长,可能影响性能和存储。用户标识、数据跟踪、临时唯一标识。
Snowflake算法生成64位长的ID,包括时间戳、机器ID和序列号。高效,支持高并发;生成的ID有序。需要配置机器ID,时间回退可能引发问题。订单ID生成、分布式系统中的唯一标识符。
数据库自增ID依赖数据库的自增字段生成唯一ID。实现简单,适用于单数据库或小规模环境。在高并发环境下可能成为瓶颈;存在单点故障风险。传统数据库应用,如用户ID、订单ID。
Leaf算法提供高性能的ID生成服务,分为ID生成器和ID分片。高性能,支持高并发。配置和维护复杂。高并发系统中的唯一ID生成,如支付系统中的交易ID。
Redis自增ID使用Redis的自增功能生成唯一ID。高性能,支持高并发。依赖Redis服务,可能成为单点故障。高并发环境中的全局唯一ID生成,如广告投放系统。

处理上述这些方案外,利用系统当前时间也能实现。一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个ID,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号。

UUID

UUID的全称是 “Universally Unique Identifier”,即通用唯一标识符。UUID是一种128位长的唯一标识符,通常表示为32个十六进制字符,分为5组,形式为xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,其中每个x表示一个十六进制数字。

UUID的生成算法有不同版本,每个版本采用不同的方法来确保唯一性。

  • UUIDv1通过结合当前时间戳和机器的MAC地址生成唯一标识符。生成过程涉及获取当前时间的100纳秒级别精度的时间戳,加上机器的MAC地址,以及一个序列号(用于处理同一时间生成多个UUID的情况)。时间戳和MAC地址确保了UUID在全球范围内的唯一性。此版本的UUID在时间上有序,但可能会暴露生成UUID的计算机信息,适用于需要时间排序的场景。
  • UUIDv3基于指定的名称和命名空间生成UUID。生成过程中,将名称与命名空间拼接后,通过MD5哈希算法计算出一个128位的哈希值,然后将其格式化为UUID。这种方法确保了相同的名称和命名空间组合总是生成相同的UUIDUUIDv3适用于需要根据相同名称和命名空间生成一致UUID的场景,例如,生成固定格式的ID
  • UUIDv4使用随机数生成128位的UUID,生成过程包括生成122位的随机数,并在UUID中设置特定的版本号(4)和变体位。这种UUID生成方法完全依赖随机数来保证唯一性,具有很高的随机性,生成过程简单且效率高。UUIDv4适合需要高性能和随机性的场景,如生成临时标识符或分布式系统中的唯一ID
  • UUIDv5类似于UUIDv3,但使用SHA-1哈希算法代替MD5来生成UUID。生成过程中,将指定的名称和命名空间拼接后,通过SHA-1计算出一个160位的哈希值,然后将其格式化为UUID,设置版本号为5。这种方法比UUIDv3更安全,适合需要基于名称生成UUID的场景,同时提供了更强的哈希安全性。

在实际开发中,UUIDv4是最常用的UUID版本。UUIDv4生成的UUID通过随机数确保唯一性,不依赖于时间戳或机器信息。在Java中,可以使用java.util.UUID类来生成UUIDv4

public class UUIDExample {public static void main(String[] args) {// 生成一个UUIDv4UUID uuid = UUID.randomUUID();System.out.println("生成的UUIDv4: " + uuid.toString());}
}

UUID一般不会作为主键使用,因为UUID太长了、占用空间大,作为主键性能太差了,且UUID不具有有序性,会导致B+树索引在写的时候有过多的随机写操作,总之就是无序的性能开销大。适合于随机生成个什么文件名、编号之类的。

Snowflake算法

Snowflake算法又称雪花算法,是一种由Twitter开发的分布式ID生成算法,用于生成唯一的64位ID。这种算法特别适合于分布式系统,能够在多个节点上高效地生成唯一ID

算法思路是是把一个64位的long型的ID,1个bit是不用的,用其中的41bits作为毫秒数,用10bits作为工作机器ID12bits作为序列号。

位数范围描述
0 - 1 bit符号位
1 - 41 bits时间戳部分
42 - 47 bits数据中心ID
48 - 63 bits工作机器ID
64 - 75 bits序列号
  • 0-1bit:不用,为啥呢?因为二进制里第一个bit为如果是1,那么都是负数,但是我们生成的ID都是正数,所以第一个bit统一都是0;
  • 1-41bit:表示的是时间戳,单位是毫秒。41bits可以表示的数字多达2^41 - 1,也就是可以标识2^41 - 1个毫秒值,换算成年就是表示69年的时间;
  • 42-47bit:记录工作机器ID,代表的是这个服务最多可以部署在2^10台机器上,也就是1024台机器。但是10bits里5个bits代表机房ID,5个bits代表机器ID。意思就是最多代表2^5个机房(32 个机房),每个机房里可以代表 2^5 个机器(32 台机器);
  • 64-75bit:这个是用来记录同一个毫秒内产生的不同ID12bits可以代表的最大正整数是2^12 - 1 = 4096 ,也就是说可以用这个12bits代表的数字来区分同一个毫秒内的4096个不同的ID

雪花算法相对来说还是比较靠谱的,毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的,能达到百万计QPS。但是雪花算法强依赖时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。为了规避雪花算法的缺点,一些国内的大厂做了改进,像美团的Leaf,百度的uid-generator,都是基于雪花算法来实现的。所以你要真是搞分布式 ID生成,如果是高并发啥的,那么用这个应该性能比较好,一般每秒几万并发的场景,也足够用了。

以下是一个Snowflake算法的简化Java实现,实际开发中可能直接引用第三方库。

public class SnowflakeIdGenerator {private final long epoch = 1622548800000L; // 自定义Epoch时间private final long dataCenterIdBits = 5L;  // 数据中心ID位数private final long workerIdBits = 5L;      // 工作机器ID位数private final long sequenceBits = 12L;     // 序列号位数private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);private final long maxWorkerId = -1L ^ (-1L << workerIdBits);private final long sequenceMask = -1L ^ (-1L << sequenceBits);private long dataCenterId; // 数据中心IDprivate long workerId;     // 工作机器IDprivate long sequence = 0L; // 序列号private long lastTimestamp = -1L; // 上次生成ID的时间戳public SnowflakeIdGenerator(long dataCenterId, long workerId) {if (dataCenterId > maxDataCenterId || dataCenterId < 0) {throw new IllegalArgumentException("DataCenterId超出范围");}if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException("WorkerId超出范围");}this.dataCenterId = dataCenterId;this.workerId = workerId;}public synchronized long generateId() {long timestamp = System.currentTimeMillis();if (timestamp < lastTimestamp) {throw new RuntimeException("系统时间回退");}if (timestamp == lastTimestamp) {sequence = (sequence + 1) & sequenceMask;if (sequence == 0) {timestamp = waitNextMillis(timestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;return ((timestamp - epoch) << (dataCenterIdBits + workerIdBits + sequenceBits)) |(dataCenterId << (workerIdBits + sequenceBits)) |(workerId << sequenceBits) |sequence;}private long waitNextMillis(long lastTimestamp) {long timestamp = System.currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return timestamp;}
}

数据库自增ID

在分布式系统中,使用数据库自增ID作为分布式ID,通常涉及在一个集中式的数据库系统中生成自增ID,然后将其分发到分布式系统的各个节点。

在数据库中创建一个自增ID的表。每次需要新的ID时,向这个表插入一条记录,然后获取即可。用数据库的自增ID功能,如MySQLAUTO_INCREMENTPostgreSQLSERIAL等,用来保证ID的唯一性和递增性。

以下是一个简化的Java代码示例,演示如何从数据库中获取自增ID

public class DistributedIdGenerator {private Connection connection;public DistributedIdGenerator(String dbUrl, String user, String password) throws Exception {connection = DriverManager.getConnection(dbUrl, user, password);}public long getNextId() throws Exception {Statement statement = connection.createStatement();// 假设存在一个名为'id_generator'的表,其中包含一个自增列'next_id'ResultSet resultSet = statement.executeQuery("SELECT next_id FROM id_generator FOR UPDATE");if (resultSet.next()) {long nextId = resultSet.getLong("next_id");// 更新IDstatement.executeUpdate("UPDATE id_generator SET next_id = next_id + 1");return nextId;} else {throw new RuntimeException("无法获取新的ID");}}public static void main(String[] args) {try {DistributedIdGenerator idGenerator = new DistributedIdGenerator("jdbc:mysql://localhost:3306/mydb", "user", "password");long newId = idGenerator.getNextId();System.out.println("生成的ID: " + newId);} catch (Exception e) {e.printStackTrace();}}
}

往一个库的一个表里插入一条没什么业务含义的数据,获取一个数据库自增的一个ID,拿到这个ID之后再往对应的分库分表里去写入。缺点是高并发存在瓶颈,优点是简单方便。适用于并发不高,但是数据量太大导致的分库分表扩容,可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。

Leaf算法

Leaf算法是一种用于生成分布式唯一ID的算法,由美团点评开发。 采用了基于Snowflake的设计思路,但将ID生成和ID管理分离。Leaf-Server负责分配ID段,Leaf-Worker负责在本地生成ID

Leaf算法通过将ID生成和ID段管理分开,能够高效地生成IDLeaf-Worker本地生成ID,减少了对中心服务器的依赖。通过分布式的Leaf-Server,Leaf算法支持高可用的ID生成。即使部分Leaf-Server出现故障,系统仍然可以继续生成ID

Leaf算法的核心思想是将ID生成和ID管理分开,利用ID段来减少对中心服务器的压力。

  1. Leaf-Server预分配一段IDLeaf-WorkerID段通常包括一个起始ID和一个结束IDLeaf-Worker在该范围内生成ID
  2. Leaf-Worker从分配的ID段中生成ID,并维护一个指针或游标来跟踪当前使用的ID。每次生成ID时,Leaf-Worker都会从本地的ID段范围中获取新的ID
  3. Leaf-Worker用完当前ID段或需要更多ID时,它会向Leaf-Server请求一个新的ID段。Leaf-Server根据当前的负载和可用资源分配新的ID段。

以下是一个简化的Java代码示例,展示如何使用Leaf算法生成ID

public class LeafWorker {private final long segmentStart;private final long segmentEnd;private final AtomicLong cursor;public LeafWorker(long segmentStart, long segmentEnd) {this.segmentStart = segmentStart;this.segmentEnd = segmentEnd;this.cursor = new AtomicLong(segmentStart);}public long getNextId() {long id = cursor.getAndIncrement();if (id > segmentEnd) {throw new RuntimeException("ID超出段范围");}return id;}
}public class LeafServer {private long currentSegmentStart = 0;private long segmentSize = 1000; // 每次分配的ID段大小public synchronized LeafWorker requestSegment() {long start = currentSegmentStart;long end = start + segmentSize - 1;currentSegmentStart = end + 1;return new LeafWorker(start, end);}
}public class Main {public static void main(String[] args) {LeafServer leafServer = new LeafServer();LeafWorker leafWorker = leafServer.requestSegment();for (int i = 0; i < 10; i++) {System.out.println("生成的ID: " + leafWorker.getNextId());}}
}

在需要高效生成唯一ID的分布式环境中,Leaf算法提供了高性能和高可用性。它适用于大规模应用和系统,其中ID生成的需求很高,且需要支持分布式架构。

Redis自增ID

Redis自增ID是基于对数据库自增的改进,原理就是利用RedisINCR命令实现ID的原子性自增。不依赖于数据库,灵活方便,且性能优于数据库,但是如果系统中没有Redis,还需要引入新的组件,会增加系统复杂度。

Redis是内存数据库,INCRINCRBY命令都是原子操作,执行速度非常快。Redis在单节点模式下可能成为单点故障。如果Redis服务出现故障,可能会影响ID生成。

以下是一个使用Java和Redis实现自增ID生成的示例代码:

public class RedisIdGenerator {private Jedis jedis;private String key;public RedisIdGenerator(String redisHost, int redisPort, String key) {this.jedis = new Jedis(redisHost, redisPort);this.key = key;}public long getNextId() {return jedis.incr(key);}public static void main(String[] args) {RedisIdGenerator idGenerator = new RedisIdGenerator("localhost", 6379, "unique_id");for (int i = 0; i < 10; i++) {System.out.println("生成的ID: " + idGenerator.getNextId());}}
}

Redis是内存数据库,具有极高的性能,Redis也能够满足实时系统需要快速生成唯一ID的情况。对于不需要复杂ID生成逻辑的应用场景,Redis自增ID是一种简单且高效的解决方案。例如,为日志条目生成唯一的ID,方便日志的管理和查询。

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

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

相关文章

数学重大错误:将无穷多各异射(直)线误为同一线

黄小宁 设本文所说集合往往是元不少于两个的集。定义&#xff1a;若数&#xff08;点&#xff09;集A可保距变为B则称A≌B。显然A≌A。 h定理1&#xff1a;数&#xff08;点&#xff09;集AB≌B的必要条件是A≌B。 证&#xff1a;⑴任何图≌本身。⑵若AB则A必可恒等变换地变…

AOP~面向切面编程介绍

AOP基础 概述 AOP&#xff1a;Aspect Oriented Programming&#xff08;面向切面编程、面向方面编程&#xff09;&#xff0c;面向特定方法的编程。 动态代理是面向切面编程最主流的实现。 SpringAOP是Spring框架的高级技术&#xff0c;旨在管理bean对象的过程中&#xff0c…

7-23学习笔记

一、异常 即程序中一些程序处理不了的特殊情况 Exception 能被程序本身处理( try-catch )&#xff0c; Error 是无法处理的(只能尽量避免)。 1、异常类 Exception 见过的异常 NullPointerException ArrayIndexoutOfBoundException等 String strnull;System.out.println(st…

【独家揭秘】模块化沙箱如何为企业筑起源代码防泄露的铜墙铁壁

在数字化转型的浪潮中&#xff0c;企业信息安全面临着前所未有的挑战&#xff0c;尤其是在源代码和敏感数据的保护方面。深信达SDC沙盒防泄密系统以其独特的模块化沙箱技术和全面的数据防泄密策略&#xff0c;为企业提供了一套高效且可靠的解决方案&#xff0c;确保源代码和重要…

夸克Android一面凉经(2024)

夸克Android一面凉经(2024) 笔者作为一名双非二本毕业7年老Android, 最近面试了不少公司, 目前已告一段落, 整理一下各家的面试问题, 打算陆续发布出来, 供有缘人参考。今天给大家带来的是《夸克Android一面凉经(2024)》。 面试职位: 智能信息-客户端开发工程师-夸克小说 技术一…

【Drools】(二)基于业务需求动态生成 DRL 规则文件:事实与动作定义详解

&#xff08;二&#xff09;基于业务需求动态生成 DRL 规则文件&#xff1a;事实与动作定义详解 在现代业务规则管理系统中&#xff0c;灵活高效地生成和管理规则至关重要。通过上一部分的DRT 规则模板&#xff08;请参考&#xff1a;&#xff08;一&#xff09;基于业务需求动…

PHP师生荣誉管理系统—计算机毕业设计源码10079

目 录 摘要 1 绪论 1.1 研究背景 1.2论文结构与章节安排 2 师生荣誉管理系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据增加流程 2.2.2 数据修改流程 2.2.3 数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.…

UDP connect 内核源码分析

1 从诡异开始 最近遇到一个线上问题&#xff0c;client 发了一个 udp 请求&#xff0c;服务器回了一个响应&#xff0c;但诡异的是&#xff0c;client 的 log 却看不到对应的处理日志。抓包发现内核发出了一个指示 udp 目的端口不可达的 icmp 报文&#xff0c;类似这样的&#…

ES6语法详解,面试必会,通俗易懂版

目录 Set的基本使用WeakSet 使用Set 和 WeakSet 区别内存泄漏示例&#xff1a;使用普通 Set 保存 DOM 节点如何避免这个内存泄漏MapWeakMap 的使用 Set的基本使用 在ES6之前&#xff0c;我们存储数据的结构主要有两种&#xff1a;数组、对象。 在ES6中新增了另外两种数据结构&a…

Java面试八股之@Qualifier的作用

Qualifier的作用 Qualifier 是 Spring 框架中的一个非常有用的注解&#xff0c;它主要用于解决在依赖注入过程中出现的歧义问题。当 Spring 容器中有多个相同类型的 Bean 时&#xff0c;Qualifier 可以帮助指明应该使用哪一个具体的 Bean 进行注入。 Qualifier 的作用&#x…

成为git砖家(7): posh-git的安装和使用

文章目录 1. PowerShell 里的 git 默认使用体验不够好2. posh-git 介绍2.1 安装 posh-git2.2 PS1 显示的内容2.3 补全分支 1. PowerShell 里的 git 默认使用体验不够好 在 Windows 系统上&#xff0c;安装了 git for windows 后&#xff0c; git bash 里的体验确实不错。 但是…

C# 获取 Excel 文件的所有文本数据内容

目录 功能需求 范例运行环境 关键代码 组件库引入 获取Excel文件的文本内容 总结 功能需求 获取上传的 EXCEL 文件的所有文本信息并存储到数据库里&#xff0c;可以进一步实现对文件内容资料关键字查询的全文检索。有助于我们定位相关文档&#xff0c;基本实现的步骤如下&…

零代码拖拽,轻松搞定GIS场景编辑

在三维GIS领域&#xff0c;编辑场景和处理影像数据通常是一个复杂且费时的过程&#xff0c;但现在有了山海鲸可视化&#xff0c;这一切都变得简单有趣。这款免费可视化工具为您提供了零代码拖拽式编辑的体验&#xff0c;让您无需编程知识就能轻松创建和优化GIS场景。通过直观的…

Hive多维分析函数——With cube、Grouping sets、With rollup

有些指标涉及【多维度】的聚合&#xff0c;大的汇总维度&#xff0c;小的明细维度&#xff0c;需要精细化的下钻。 grouping sets&#xff1a; 多维度组合&#xff0c;组合维度自定义&#xff1b;with cube&#xff1a; 多维度组合&#xff0c;程序自由组合&#xff0c;组合为…

大数据:数据标准化及质量管控方案

本方案是一套全面的解决方案&#xff0c;旨在为企业构建科学、规范的数据管理体系&#xff0c;确保数据的准确性、一致性、完整性、合理性、及时性和有效性&#xff0c;从而支撑业务数据的高效应用与正确决策。以下是对该方案的详细介绍&#xff1a; 一、方案概述 本数据标准…

迎峰度夏,应急备电:应急电源和燃油发电机哪个好?应急电源选购

在电网迎峰度夏的严峻挑战面前&#xff0c;铂陆帝应急电源以其卓越的性能和可靠性&#xff0c;成为了不可或缺的电力保障伙伴。与燃油发电机相比&#xff0c;铂陆帝应急电源在多个方面均展现出显著的优势。 更高效稳定&#xff0c;性能卓越 铂陆帝应急电源具备出色的性能和稳定…

GPIO子系统

1. GPIO子系统视频概述 1.1 GPIO子系统的作用 芯片内部有很多引脚&#xff0c;这些引脚可以接到GPIO模块&#xff0c;也可以接到I2C等模块。 通过Pinctrl子系统来选择引脚的功能(mux function)、配置引脚&#xff1a; 当一个引脚被复用为GPIO功能时&#xff0c;我们可以去设…

PySide(PyQt)的QPropertyAnimation(属性动画)

学不完&#xff0c;根本学不完:(&#xff0c;感觉逐渐陷入了学习深渊。。。 QPropertyAnimation 是 PySide(PyQt) 中一个用于在时间轴上平滑地改变对象属性的类。它常用于制作动画效果&#xff0c;比如移动、缩放或改变透明度等。 基本概念 QPropertyAnimation 是 Qt …

GPT5发布时间预测,即将到来的GPT5

GPT-5&#xff1a;未来的展望与功能预测 随着人工智能技术的飞速发展&#xff0c;生成式预训练模型&#xff08;GPT&#xff09;已经成为自然语言处理领域的核心技术。从 GPT-1 到目前的 GPT-4&#xff0c;每一代模型都带来了显著的进步和变革。那么&#xff0c;GPT-5 的到来将…

【环境搭建问题】linux服务器安装conda并创建虚拟环境

1.检查有没有conda 首先看root文件夹下有没有anaconda或者conda 没有的话就要先下载安装conda&#xff1a; https://repo.anaconda.com/archive/index.html 在这个链接下找自己需要的。服务器一般为linux&#xff0c;所以我这里选择的是&#xff1a; 2.安装conda 下载安装…