在复杂的系统中,每个业务实体都需要使用ID做唯一标识,以方便进行数据操作。例如,每个用户都有唯一的用户ID,每条内容都有唯一的内容ID,甚至每条内容下的每条评论都有唯一的评论ID。

4.1.1 全局唯一与UUID

在互联网还未普及的年代,由于用户量少、网络交互形式单调,互联网产品后台数据库使用单体架构就可以满足日常服务的需求。当时每个业务实体都对应数据库中的一个数据表,每条数据都简单地使用数据库的自增主键作为唯一ID。

近年来,随着互联网用户的爆发式增长,数据库从单体架构演进到分库分表的分布式架构,同一个业务实体的数据被分散到多个数据库中。由于数据表之间相互独立,在插入数据时会生成相同的自增主键。此时,如果还使用自增主键作为唯一ID,就会导致大量数据的标识相同,造成严重事故。我们应该保证无论一个业务实体的数据被分散到多少个数据库中,每条数据的唯一ID都是全局的,这个全局唯一ID就是分布式唯一ID。

RFC 4122 规范中定义了通用唯一识别码(Universally Unique Identifier, UUID),它是计算机体系中用于识别信息的一个128位标识符。UUID按照标准方法生成时,在实际应用中具有唯一性,UUID重复的概率可以忽略不计。JDK 1.5在语言层面实现了 UUID,可以轻松生成全球唯一ID:

import java.util.UUID;public class idgenerator {public static void main(String[] args) {String uuid = UUID.randomUUID().toString();System.out.printin(uuid);}
}

UUID的标准格式由32个十六进制数字组成,并通过连字符-分隔成8-4-4-4-12共 36 个字符的形式。例如,6a0d3e6f-allc-4b7d-bb35-c4c530a456b0123e4567-e89b-12d3- a456-426655440000。这种唯一ID的生成方式足够简单,利用本地计算即可生成全球唯一ID。不过,UUID具有一些缺点:

  • UUID字符串需要占用36字节的存储空间,如果每条数据都携带UUID,那么在海量数据场景下存储空间消耗较大。
  • 此外,UUID是无数据规律的长字符串,如果将其用作数据库主键,则会导致数据在磁盘中的位置频繁变动,严重影响数据库的写操作性能。

4.1.2 唯一ID生成器的特点

UUID仅适合数据量不大的场景,比如一个存储集群使用UUID标识每个数据分区。真正可用于海量数据场景的唯一ID生成器,除保证ID不可重复外,还应该具有如下特点。

  • 空间占用小:作为每条数据都携带的字段,唯一ID不应该占用过多的存储空间。
  • 高并发与高可用性:唯一ID生成器是大部分业务服务的重要依赖方,唯一ID的生成操作需要做到高并发无压力,维持长期高可用性。
  • 唯一ID可用作数据库主键:为了不对数据库的写操作造成负面影响,需要保证唯一ID对数据库主键友好。

前两点很好理解,最后一点,什么样的唯一ID才对数据库主键友好呢?我们以MySQL数据库的InnoDB引擎为例。InnoDB使用基于磁盘的B+树表示数据表,并以主键作为索引,即B+树按照主键从小到大的顺序排列数据。

如图4-1所示,B+树的节点使用默认为16KB大小的数据页(Page)表示,其中:

image-20250321180226643

  • 节点分为叶节点和非叶节点,底层是叶节点。
  • 同层数据页之间相互组成双向链表。
  • 非叶节点仅保存N个主键作为指向下一层N个数据页的索引,主键从小到大排列。
  • 叶节点保存实际的数据,叶节点组成的双向链表上的所有数据按照主键从小到大的顺序排列。

使用自增性质的字段作为InnoDB数据表的主键是一个很好的选择,每次写入新数据时,数据都被顺序添加到对应数据页的尾部;一个数据页写满后,B+树自动开辟一个新的数据页。

如图4.2所示,主键值为203的新数据被插入数据页2的尾部,这样一来,B+树将会形成一个较为紧凑的索引结构,空间利用率较高;而且,每次插入数据时也不需要移动已有的数据,时间开销很小。

image-20250321180609696

而如果使用非自增性质的字段(比如身份证号码、电话号码)作为主键,由于主键值较为随机,新数据可能要被插入数据页中间的某个位置。如图4-3所示,主键值为15的新数据只能被插入数据1和数据30之间,数据30、50、90都需要向后移动。

image-20250321180702306

为了给新数据腾出位置,B+树不得不将已有的数据向后移动——如果数据页已满,则会进行多次分页操作。频繁的数据移动和分页操作使得B+树在磁盘上产生大量的碎片,且时间开销很大。因此,官方建议尽量使用自增性质的字段作为InnoDB数据表的主键

为了成为自增性质的主键,唯一ID生成器生成的唯一ID在数值上应该是递增的,这样的唯一ID对数据库主键就是友好的。

占用8字节(64位)的long类型整数适合用作唯一ID,因为:

  • long类型虽然占 用的空间较小,但是可表示的ID范围却非常大
  • long类型整数很容易实现递增的效果。

至此,本章的议题已经明确:设计一个可以生成递增的long类型唯一ID的生成器

4.1.3 单调递增与趋势递增

在正式开始设计唯一ID生成器之前,我们还需要解释一下递增。递增可以分为单调递增趋势递增,从技术实现的角度来看,它们的差异较大。

  • 单调递增:T表示绝对时间点,如果Tn+1>TnT_{n+1} > T_nTn+1>Tn,则一定有F(Tn+1)>F(Tn)F(T_{n+1}) > F(T_n)F(Tn+1)>F(Tn)。如果唯一ID生成器生成的ID单调递增,则说明下一次获取到的ID一定大于上一次获取到的ID。
  • 趋势递增:T表示绝对时间点,如果Tn+1>TnT_{n+1} > T_nTn+1>Tn,则大概率有F(Tn+1)>F(Tn)F(T_{n+1}) > F(T_n)F(Tn+1)>F(Tn)。虽然在一小段时间内数据有乱序的情况,但是从整体趋势上看,数据是递增的。

单调递增和趋势递增的数据特点如图4-4所示。

image-20250321190719896

虽然我们在4.1.2节中已经讨论了唯一ID应该是递增的,但无奈受限于全局时钟、延迟等分布式系统问题,单调递增的唯一ID生成器的设计方案往往会有较大的局限性,与此相比,趋势递增的唯一 ID生成器更受业界欢迎。接下来具体介绍这两种递增类型的唯一ID生成器设计方案的差别。

总结

在分布式架构中,如果还使用自增主键作为唯一ID,会发生什么问题呢?

数据库从单体架构演进到分库分表的分布式架构,同一个业务实体的数据被分散到多个数据库中。由于数据表之间相互独立,在插入数据时会生成相同的自增主键。此时,如果还使用自增主键作为唯一ID,就会导致大量数据的标识相同,造成严重事故。

什么是UUID呢?

UUID的标准格式由32个十六进制数字组成,并通过连字符-分隔成8-4-4-4-12共 36 个字符的形式。例如,6a0d3e6f-allc-4b7d-bb35-c4c530a456b0123e4567-e89b-12d3- a456-426655440000

UUID的缺点?

  • UUID字符串需要占用36字节的存储空间,如果每条数据都携带UUID,那么在海量数据场景下存储空间消耗较大。

  • UUID是无数据规律的长字符串,如果将其用作数据库主键,则会导致数据在磁盘中的位置频繁变动,严重影响数据库的写操作性能。

唯一ID生成器的特点?

  • 空间占用小:作为每条数据都携带的字段,唯一ID不应该占用过多的存储空间。
  • 高并发与高可用性:唯一ID生成器是大部分业务服务的重要依赖方,唯一ID的生成操作需要做到高并发无压力,维持长期高可用性。
  • 唯一ID可用作数据库主键:为了不对数据库的写操作造成负面影响,需要保证唯一ID对数据库主键友好。

为什么使用自增性质的字段作为InnoDB数据表的主键是一个很好的选择?

  • 使用自增性质的字段,每次写入新数据时,数据都被顺序添加到对应数据页的尾部;一个数据页写满后,B+树自动开辟一个新的数据页。
  • 使用非自增性质的字段(比如身份证号码、电话号码)作为主键,由于主键值较为随机,新数据可能要被插入数据页中间的某个位置。为了给新数据腾出位置,B+树不得不将已有的数据向后移动——如果数据页已满,则会进行多次分页操作。频繁的数据移动和分页操作使得B+树在磁盘上产生大量的碎片,且时间开销很大。

为什么占用8字节(64位)的long类型整数适合用作唯一ID呢?

  • long类型虽然占用的空间较小,但是可表示的ID范围却非常大
  • long类型整数很容易实现递增的效果。

什么是单调递增和趋势递增?

  • 单调递增:T表示绝对时间点,如果Tn+1>TnT_{n+1} > T_nTn+1>Tn,则一定有F(Tn+1)>F(Tn)F(T_{n+1}) > F(T_n)F(Tn+1)>F(Tn)。如果唯一ID生成器生成的ID单调递增,则说明下一次获取到的ID一定大于上一次获取到的ID。
  • 趋势递增:T表示绝对时间点,如果Tn+1>TnT_{n+1} > T_nTn+1>Tn,则大概率有F(Tn+1)>F(Tn)F(T_{n+1}) > F(T_n)F(Tn+1)>F(Tn)。虽然在一小段时间内数据有乱序的情况,但是从整体趋势上看,数据是递增的。

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

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

相关文章

图论水题日记

cf1805D 题意 给定一棵树,规定dis(u,v)≥kdis(u,v) \geq kdis(u,v)≥k时(u,v)(u,v)(u,v)之间存在一条无向边,求k(1,2,...n)k(1,2,...n)k(1,2,...n)时图中的连通块个数 思路 前置知识:树上一点到其最远的点一定是树直径的两个端点之一若一个点…

自定义线程

每个程序至少有一个线程 —— 主线程 主线程是程序的起点,你可以从它开始创建新的线程来执行任务。为此,你需要创建自定义线程,编写在线程中执行的代码,并启动它。 通过继承创建自定义线程 创建新线程有两种主要方式:继…

2025真实面试试题分析-安卓客户端开发

以下是对安卓客户端开发工程师面试问题的分类整理、领域占比分析及高频问题精选(基于​​85道问题,总出现次数118次​​)。按技术领域整合为​​7大核心类别​​,按占比排序并精选高频问题标注优先级(1-5🌟…

算法学习笔记:29.拓扑排序——从原理到实战,涵盖 LeetCode 与考研 408 例题

拓扑排序(Topological Sorting)是一种针对有向无环图(DAG)的线性排序算法,它将图中的顶点按照一定规则排列,使得对于图中的任意一条有向边 u→v,顶点 u 都排在顶点 v 之前。拓扑排序在任务调度、…

利用Web3加密技术保障您的在线数据安全

在这个信息爆炸的数字化时代,保护个人和企业数据安全变得尤为重要。Web3技术以其去中心化和加密特性,为在线数据安全提供了新的解决方案。本文将探讨Web3技术如何通过加密技术保障您的在线数据安全,并介绍如何有效利用这些技术。 什么是Web3技…

Vue实现el-checkbox单选并回显选中

先说需求 我要在页面进行checkbox单选并回显 第一步先把基本的页面写好噢&#xff1a;vue代码&#xff1a;别忘了写change啊<el-form-item label"按钮颜色:" prop"menuColor"><el-checkbox-group v-model"buttonColor" change"bin…

动态规划--序列找优问题【1】

一、说明 动态规划似乎针对问题很多&#xff0c;五花八门&#xff0c;似乎每一个问题都有一套具体算法。其实不是的&#xff0c;动态规划只有两类&#xff1a;1&#xff09;针对图的路径问题 2&#xff09;针对一个序列的问题。本篇讲动态规划针对序列的算法范例。 二、动态规划…

独家|百度副总裁尚国斌即将离职,此前统筹百度地图;行业搜索及智能体业务总经理谢天转岗IDG

百度人事再变动。作者|文昌龙编辑|杨舟据「市象」了解&#xff0c;近期&#xff0c;百度副总裁尚国斌即将离职。公开资料显示&#xff0c;尚国斌2010年毕业于南开大学&#xff0c;2012年加入百度&#xff0c;先后在商业分析部、集团战略办、智能驾驶事业群工作。尚国斌同样也在…

Qt 网络编程进阶:HTTP 客户端实现

在 Qt 应用程序中&#xff0c;实现高性能、可靠的 HTTP 客户端是常见需求。Qt 提供了丰富的网络模块&#xff0c;包括 QNetworkAccessManager、QNetworkRequest 和 QNetworkReply 等类&#xff0c;用于简化 HTTP 通信。本文将深入探讨 Qt 网络编程中 HTTP 客户端的进阶实现&…

Python Requests-HTML库详解:从入门到实战

一、库简介 Requests-HTML是Python中集网络请求与HTML解析于一体的全能型库&#xff0c;由知名开发者Kenneth Reitz团队维护。它完美结合了Requests的易用性和Parsel的选择器功能&#xff0c;并内置JavaScript渲染引擎&#xff0c;特别适合现代动态网页抓取。最新版本&#xf…

基于springboot的小区车位租售管理系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

Kafka 如何优雅实现 Varint 和 ZigZag 编码

ByteUtils 是 Kafka 中一个非常基础且核心的工具类。从包名 common.utils 就可以看出&#xff0c;它被广泛用于 Kafka 的各个模块中。它的主要职责是提供一套高效、底层的静态方法&#xff0c;用于在字节缓冲区 (ByteBuffer)、字节数组 (byte[]) 以及输入/输出流 (InputStream/…

局域网 IP地址

很多童鞋搞不清楚局域网ip是什么? 什么是局域网 IP 地址? 局域网 IP 地址,也称为 私有 IP 地址(Private IP Address),是用于在局域网内部标识设备的地址。这些地址不能直接在互联网上被访问,通常由路由器自动分配,用于设备之间的内部通信。 局域网 IP 地址的分类 根…

k8s的service、deployment、探针详解

1.k8s组成图2.service和deployment的流量转发图# Deployment 定义容器端口 apiVersion: apps/v1 kind: Deployment metadata:name: myapp spec:template:spec:containers:- name: nginximage: nginxports:- containerPort: 80 # 容器监听 80name: http # 端口命名&…

【PostgreSQL教程】PostgreSQL中json类型与jsonb类型的区别

博主介绍:✌全网粉丝23W+,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物联网、机器学习等设计与开发。 感兴趣的可…

牛客刷题记录01

除2&#xff01; 目录 除2&#xff01; 题目描述&#xff1a; ​编辑 题目解析&#xff1a; 代码实现&#xff1a; 数组中两个字符串的最小距离__牛客网 题目描述&#xff1a; 题目解析&#xff1a; 代码实现&#xff1a; 除2&#xff01; 题目描述&#xff1a; 给一个…

Docker Compose UI远程访问教程:结合贝锐花生壳实现内网穿透

对于很多刚接触Docker的用户来说&#xff0c;命令行操作总带着一丝“劝退感”。尤其是要在Windows上部署服务、开放端口、配置参数时&#xff0c;稍有不慎就容易出错。有没有办法像网页后台一样&#xff0c;用图形界面来管理Docker项目呢&#xff1f;答案是&#xff1a;有&…

HF83311_VB1/HF83311Q_VB1:高性能USB HiFi音频解码器固件技术解析

引言随着高品质音频体验需求的不断增长&#xff0c;音频解码器固件的性能和功能成为决定音频设备品质的关键因素。本文将介绍一款基于XMOS XU316技术的高性能USB HiFi音频解码器固件——HF83311_VB1/HF83311Q_VB1&#xff0c;这是一款专为USB HiFi音频应用设计的软件解决方案。…

[ComfyUI] -入门1-ComfyUI 是什么?比 Stable Diffusion WebUI 强在哪?

ComfyUI 是一个开源的、节点可视化界面,用于构建与执行 Stable Diffusion 图像生成流程。它把复杂的生成过程拆解为许多“节点”(如提示编码、采样器、控制网络等),用户通过连接节点,就能自由编排工作流 。这种设计适合开发者与进阶用户,更便于微调、多分支与复用流程。 …

[python][flask]flask接受get或者post参数

在 Flask 中&#xff0c;可以通过 request 对象来获取客户端通过 GET 或 POST 方法发送的参数。以下是如何在 Flask 中接收 GET 和 POST 参数的详细说明&#xff1a;1. 接收 GET 参数GET 请求的参数通常通过 URL 的查询字符串传递。例如&#xff0c;对于 URL http://example.co…