声明:本文仅作学习使用。 在这一章中,你被要求在分布式系统中设计一个唯一的ID生成器。您首先想到的可能是在传统数据库中使用带有auto_increment属性的主键。但是,auto_increment不能在分布式环境中工作,因为单个数据库服务器不够大,并且以最小的延迟跨多个数据库生成唯一的id具有挑战性。
以下是一些独特id的例子:
步骤1 -理解问题并确定设计范围
提出澄清性问题是解决任何系统设计面试问题的第一步。
下面是一个面试官与面试官互动的例子: 候选人:唯一的id有什么特点?面试官:id必须是唯一的和可分类的。
候选人:对于每条新记录,ID是否增加1?采访者:ID随时间增加,但不一定只增加1。晚上创建的id大于同一天早上创建的id。
候选人:id只包含数值吗?采访者:是的,没错。
候选人:身份证长度有什么要求?面试官:id应该是64位的。
候选人:系统的规模是多少?采访者:系统应该能够每秒生成10,000个id。
以上是一些你可以问面试官的问题。理解需求和澄清歧义是很重要的。对于这个面试问题,要求如下:
-
id必须是唯一的。
-
id仅为数值。
-
id适合64位。
-
id按日期排序。
-
每秒生成超过10,000个唯一id的能力。
步骤2-提出顶层设计并且获得理解支持
在分布式系统中,可以使用多个选项来生成唯一的id。我们考虑的选项有:
- 多主复制
- 通用唯一标识符(UUID)
- 票据服务器
- Twitter雪花方法让我们看看它们中的每一个,它们是如何工作的,以及每个选项的优缺点。
多主复制
如图7-2所示,第一种方式是多主复制
这种方法使用数据库的auto_increment特性。下一个ID不是增加1,而是增加k,其中k是正在使用的数据库服务器的数量。如图7-2所示,下一个生成的ID等于同一服务器上一个生成的ID加2。这解决了一些可伸缩性问题,因为id可以随着数据库服务器的数量而扩展。
然而,这种策略有一些主要的缺点:
- 难以扩展到多个数据中心
- id不随时间在多个服务器上增加。
- 当添加或删除服务器时,它不能很好地扩展
UUID
UUID是获得唯一id的另一种简单方法。UUID是一个128位的数字,用于识别计算机系统中的信息。UUID重复的概率非常低。
引用维基百科的话,“在大约100年的时间里,每秒生成10亿个uuid之后,创建UUID重复的概率将达到50%”。
UUID示例如下:09c93e62-50b4-468d-bf8a-c07e1040bfb2。uuid可以独立生成,而不需要服务器之间的协调。uuid设计如图7-3所示。
在这个设计中,每个web服务器包含一个ID生成器,并且一个web服务器负责独立生成ID。
优点:
-
生成UUID很简单。服务器之间不需要协调,因此不会出现任何同步问题。
-
系统易于扩展,因为每个web服务器负责生成它们所使用的id。ID生成器可以很容易地扩展与web服务器。
缺点:
-
id是128位长,但我们的要求是64位。
-
id不随时间增长。
-
id可以是非数字的。
票服务器
票据服务器是生成唯一id的另一种有趣方式。Flicker开发了票证服务器来生成分布式主键[2]。值得一提的是这个系统是如何工作的。
其思想是在单个数据库服务器(Ticket server)中使用集中的auto_increment特性。要了解更多,请参阅flicker的工程博客文章[2]。
优点:
-
数字id。
-
易于实现,适用于中小型应用程序。
缺点:
- 单点故障。单票证服务器意味着如果票证服务器宕机,依赖于它的所有系统都将面临问题。为了避免单点故障,我们可以设置多个票证服务器。然而,这会带来新的挑战,比如数据同步。
推特雪花算法
上面提到的方法让我们了解了不同的ID生成系统是如何工作的。但是,没有一个符合我们的具体要求;因此,我们需要另一种方法。Twitter独有的名为“snowflake”的ID生成系统很有启发性,可以满足我们的需求。
分而治之是我们的朋友。我们不直接生成ID,而是将ID划分为不同的部分。64位ID的布局如图7-5所示。
下面将对每个部分进行解释。
-
符号位:1位。它总是0。这是为将来使用而保留的。它可能用于区分有符号数和无符号数。
-
时间戳:41位。自epoch或自定义epoch以来的毫秒数。我们使用Twitter雪花默认纪元1288834974657,相当于Nov 04, 2010, 01:42:54 UTC。
-
数据中心ID: 5位,即2 ^ 5 = 32个数据中心。
-
机器ID: 5位,每个数据中心有2 ^ 5 = 32台机器。
-
序列号:12位。对于在该机器/进程上生成的每个ID,序列号增加1。该数字每毫秒重置为0。
步骤3-底层设计
在高级设计中,我们讨论了在分布式系统中设计唯一ID生成器的各种选项。我们选择了一种基于Twitter雪花ID生成器的方法。让我们深入了解设计。为了刷新我们的记忆,下面重新列出了设计图。
TimeStamp
最重要的41位组成时间戳部分。随着时间戳的增长,id可以按时间排序。图7-7显示了二进制表示如何转换为UTC的示例。您也可以使用类似的方法将UTC转换回二进制表示。
可以用41位表示的最大时间戳是2 ^ 41 - 1 = 2199023255551毫秒(ms),这给了我们:~ 69年= 2199023255551毫秒/ 1000秒/ 365天/ 24小时/ 3600秒。这意味着ID生成器将工作69年,如果自定义纪元时间接近今天的日期,则会延迟溢出时间。69年后,我们将需要一个新的纪元时间或采用其他技术来迁移id
序列号
序列号是12位,这给了我们2 ^ 12 = 4096个组合。该字段为0,除非同一服务器在一毫秒内生成多个ID。理论上,一台机器每毫秒最多可以支持4096个新id。
步骤4-打包
在本章中,我们讨论了设计唯一ID生成器的不同方法:multimaster复制、UUID、票据服务器和Twitter雪花状唯一ID生成器。我们选择snowflake,因为它支持我们所有的用例,并且在分布式环境中是可扩展的。
-
如果在面试结束时有额外的时间,这里有一些额外的谈话要点:在我们的设计中,我们假设ID生成服务器具有相同的时钟。当服务器在多个核心上运行时,这个假设可能不成立。同样的挑战也存在于多机器场景中。时钟同步的解决方案超出了本书的范围;然而,了解问题的存在是很重要的。网络时间协议是解决这个问题的最流行的解决方案。有兴趣的读者可参考参考资料。
-
节长度调整。例如,对于低并发性和长期应用程序,减少序列号但增加时间戳位是有效的。
-
高可用性。由于ID生成器是一个任务关键型系统,因此它必须具有高可用性。
恭喜你走了这么远!现在给自己点鼓励吧。好工作!