面试场景题系列:分布式系统中的唯一ID生成器

ops/2024/12/28 0:13:00/

1.场景需求需求界定

•ID必须是唯一的。

•ID只包含数字。

•ID长为64位。

•ID按日期排序。

•可以每秒生成超过10,000个唯一ID。

2.高层级的设计

在分布式系统中,有多个方法可以用来生成唯一ID。我们考虑的方法有:

•多主复制(Multi-master Replication)。

•通用唯一标识符(Universally Unique Identifier,UUID)。

•工单服务器(Ticket Server)。

•推特的雪花(Snowflake)系统。

我们来看看它们的工作原理以及优缺点。

2.1.多主复制

图-1

这个方法利用了数据库的自增特性。我们并不是把下一个ID加1,而是加k,这里k是正在使用的服务器数量。在图-1中,生成的下一个ID等于同一个服务器上的前一个ID加2。这种方法解决了一些可扩展性问题,因为ID可以随着服务器数量的增加而同步扩展。但是这个方法也有一些重大缺点。

•很难与多个数据中心一起扩展,需要进行额外的同步和协调操作。

•在分布式环境下,多个服务器同时生成ID,可能导致ID并不连续,也即ID并不随时间递增。

•当服务器被添加或者移除时,ID不能很好地随之变化。

2.2 UUID

UUID是另一种获取唯一ID的简单方法。UUID是一个128位的数字,用来标识计算机系统中的信息。UUID重复的概率非常低。这里引用维基百科的说法,“每秒产生10亿个UUID且持续约100年,产生一个重复UUID的概率才达到50%。”。下面是一个UUID的例子:09c93e62-50b4-468d-bf8a-c07e1040bfb2。UUID可以独立地生成而不需要在服务器之间做任何协调。图-2展示了采用UUID的设计。在这个设计中,每个Web服务器都含有一个ID生成器且负责独立地生成ID。

图-2

UUID方法的优点是:

•生成ID很简单。服务器之间不需要任何协调,所以不会有任何同步问题。

•系统易于扩展,因为每个Web服务器只负责生成它们自己使用的ID。ID生成器可以很容易地随Web服务器一起扩展。

其缺点是:

•ID长128位,但是我们要求的是64位。

•ID并不随时间增加。

•ID可能是非数字的。

2.3 工单服务器

工单服务器也是生成唯一ID的重要方法。Flicker研发了工单服务器来生成分布式主键。值得一提的是这个方法的工作原理。这个方法的思想是利用中心化的单数据库服务器(工单服务器)的自增特性(参见图-3)。

图-3

工单服务器方法的优点是:

•ID为数字。

•容易实现,适用于中小型应用。

其缺点是存在单点故障。单个工单服务器意味着,如果这个服务器发生故障,所有依赖于它的系统就都会面临问题。为了避免单点故障,我们可以设置多个工单服务器。但是这会引入新的挑战,比如数据同步问题。

2.4雪花系统

前面介绍了几个ID生成方法的原理,但是这些方法中没有一个满足我们的特定需求,因此我们需要另一种方法。推特的唯一ID生成系统叫作“Snowflake”(雪花),它很有启发性,而且能满足我们的需求。分布解决是个好办法。我们不直接生成一个ID,而是把一个ID分成不同的部分。图-4展示了一个64位ID的构成。

图-4

每个部分的含义如下所述。

•符号位(1位):它始终为数字0,留作未来使用。它有可能被用来区分有符号数和无符号数。

•时间戳(41位):它是从纪元或者自定义纪元开始以来的毫秒数。我们使用Snowflake默认纪元(epoch)1,288,834,974,657,相当于UTC时间2010年11月4日01:42:54。

•数据中心ID(5位):最多可以有32个(25)数据中心。

•机器ID(5位):每个数据中心最多可以有32台(25)机器。

•序列号(12位):对于某个机器/进程,每生成一个ID,序列号就加1。这个数字每毫秒开始时都会被重置为0。

3.深入设计

我们讨论了在分布式系统中设计唯一ID生成器的各种方法,最后选择了基于推特Snowflake ID生成器的方法。接下来,我们进行深入的设计。

图-5

数据中心ID和机器ID通常在起始阶段就选好了,一旦系统运行起来,这两个部分就是固定的。对数据中心ID和机器ID所做的任何更改都需要仔细审查,因为对这些值的意外改动可能会导致ID冲突。时间戳和序列号是在ID生成器运行后才生成的。

3.1时间戳

41位的时间戳是ID中最重要的部分。随着时间的推移,时间戳不断增长,因此ID可以按时间排序。图-6展示了一个例子,将二进制表示的时间戳转换成UTC时间。你也可以用类似的方法把UTC时间转换成二进制表示。

41位能表示的最大时间戳是241-1,即2,199,023,255,551毫秒(ms),约等于69年(计算方法为2,199,023,255,551÷1000÷365÷24÷3600)。这意味着ID生成器可以工作约69年。如果我们把纪元开始时间定制得离今天的日期足够近,就可以延迟溢出时间。69年后,我们需要一个新的纪元时间或者采用别的技术来迁移ID。

图-6

3.2序列号

序列号有12位,相当于4096种组合(212)。这个部分一般是0,除非在1毫秒内同一个服务器生成了多个ID。理论上,一个服务器每毫秒最多生成4096个新ID。

4.总结

在本文中,我们讨论了设计一个唯一ID生成器的不同方法:多主复制、UUID、工单服务器和类似推特Snowflake的唯一ID生成器。我们最后选择了Snowflake,因为它支持我们的所有用例,并且可以在分布式环境中扩展。如果在面试的最后还有一些时间,你可以讨论下面这些议题。

•时钟同步。在我们的设计里,我们假设生成ID的服务器都有同样的时钟。但是,当服务器运行在多核上时,这个假设可能并不成立。在多机器的场景中也存在同样的挑战。时钟同步的解决方案不在本文的讨论范围内;但是,知道这个问题的存在是很重要的。网络时间协议(NTP)是这个问题最流行的解决方案,感兴趣的读者可以参阅维基百科中的“Network Time Protocol”词条。

•调整ID各部分的长度。比如,对于低并发且长时间持续运行的应用,减少序列号部分的长度,增加时间戳部分的长度,生成的ID会更高效。

•高可用性。因为ID生成器是一个非常关键的系统,所以它必须是高可用的。


http://www.ppmy.cn/ops/145514.html

相关文章

最新的强大的文生视频模型Pyramid Flow 论文阅读及复现

《PYRAMIDAL FLOW MATCHING FOR EFFICIENT VIDEO GENERATIVE MODELING》 论文地址:2410.05954https://arxiv.org/pdf/2410.05954 项目地址: jy0205/Pyramid-Flow: 用于高效视频生成建模的金字塔流匹配代码https://github.com/jy0205/Pyram…

linux服务器上CentOS的yum和Ubuntu包管理工具apt区别与使用实战

在 CentOS 7 上,系统默认使用 yum 作为包管理工具,而不是 apt。apt 是为 Debian 和 Ubuntu 系统设计的,不能在 CentOS 或其他基于 RHEL 的发行版上直接使用。 如果你希望继续使用 CentOS 7,并管理软件包,你应该使用 y…

Zookeeper 底层原理解析

一、引言 在分布式系统的浩瀚星空中,Zookeeper 宛如一颗最为闪耀的导航星,为众多分布式应用指引方向、保驾护航。无论是大名鼎鼎的 Hadoop、HBase,还是其他各类复杂的分布式架构,Zookeeper 都扮演着不可或缺的关键角色。它如同一…

JavaScript文件端点提取与安全分析:两种高效实用的方法

提取JS文件中的所有端点(Endpoints) JavaScript文件中包含了大量的信息,对于安全研究人员来说,提取这些文件中的API端点是发现潜在漏洞的重要环节之一。在本篇文章中,我们将介绍两种高效提取JavaScript文件端点的方法。以下方法主要应用于渗透测试场景,尤其是针对目标域…

关于 K8s 的一些基础概念整理-补充【k8s系列之二】

〇、前言 本文继续整理下 K8s 的一些基础概念,作为前一篇概念汇总的补充。 前一篇博文链接:关于 K8s 的一些基础概念整理【k8s系列之一】_集群 master节点 控制节点 宿主机-CSDN博客 一、详情 1.1 Label Label 在 k8s 中是一个非常核心的概念&#xf…

微服务——数据管理与一致性

1、在微服务架构中,每个微服务都有自己的数据库,这种设计有什么优点和挑战? 优点挑战服务自治:每个微服务可独立选择适合自己的数据库类型。数据一致性:跨微服务的事务难以保证强一致性。故障隔离:一个微服…

【手写数据库内核miniToadb】第1天 模拟数据库流程,剖析数据库内核的组成结构

​专栏内容: 手写数据库toadb 本专栏主要介绍如何从零开发,开发的步骤,以及开发过程中的涉及的原理,遇到的问题等,让大家能跟上并且可以一起开发,让每个需要的人成为参与者,在开源无限的公众号更…

解决在vue3+vite+element-plus 中echarts在el-dialog无法正常显示问题

核心&#xff1a;在dom加载完成后调用echarts实例 的resize()方法 这里是一个例子 这里封装一个echarts <template><div class"container" ref"container"></div> </template> <script lang"ts" setup> import {…