如何在分布式系统中生成唯一的标识符:6个关键策略

超级欧派课程 2024-03-30 03:11:31

在分布式环境中,两个节点可以同时分配标识符,挑战在于确保这些标识符保持唯一,避免冲突并确保系统一致性。

想象一下,在一个分布式系统中,有两个节点同时运行,它们都负责为存储在共享存储中的对象生成标识符。

如何确保这些节点生成的标识符是唯一的,不会发生冲突?

有多种策略可满足这些要求,每种策略都适用于特定的要求:

唯一性:生成的每个标识符在系统的所有节点中都是唯一的。可扩展性:系统应能够以高速率生成标识符而不发生冲突。单调递增性:(如果需要)标识符应随时间递增。

我了解到许多生成标识符的方法,让我们一起更详细地了解其中的几种:

UUID(通用唯一标识符)。NanoID。序列号。ObjectID。Twitter Snowflake。Sonyflake(受到Snowflake启发)。

每种方法都有其自身的优点和挑战,在我们逐个讨论它们时,我会分享我的思考。

每周接收洞察性文章并培养阅读习惯:

1. UUID 或 GUID(128位)

当谈到生成唯一标识符时,UUID(通用唯一标识符)是首先想到的。

UUID由32个十六进制字符组成。请记住,每个字符占4位。因此,总共是128位。当加上4个连字符时,你会看到36个字符:

6e965784–98ef-4ebf-b477–8bd14164aaa45fd6c336-48c4-4510-bfe5-f7928a83a3e20333be18-5ecc-4d7e-98d4-80cc362e4ade

有5种常见的UUID类型:

版本1 - 基于时间和MAC地址:此UUID使用计算机的MAC地址和当前时间。版本2 - DCE安全:类似于版本1,但包含其他信息,如POSIX UID或GID。版本3 - 基于名称的MD5:它接受一个命名空间和一个字符串,然后使用MD5生成UUID。版本4 - 随机性:每个字符都是随机选择的。版本5 - 基于名称的SHA1:类似于版本3,但它使用SHA-1而不是MD5。… 你可能还考虑其他草案,如版本6 - 重新排序的时间和版本7 - Unix纪元时间等,这是最新提议中的一些。

我现在不会详细介绍每个版本的细节。但如果你不确定选择哪个版本,我发现版本4 - 随机性是一个很好的起点。它简单而有效。

“随机和唯一?这怎么可能?”

其魔力在于极低的碰撞几率。

引用维基百科的数据,想象每秒生成10亿个UUID,持续86年,才有50%的几率得到一次匹配。

“为什么有人说UUID只有122位,而不是128位?”

当人们谈论UUID时,通常指的是最常见的UUID版本:版本4(或"v4")。版本4的UUID使用随机数生成,因此具有最高的唯一性。由于UUID的128位中的6位已经用于特定目的(其中4位告诉我们这是版本4(或"v4"),另外2位用于保留变体信息),因此实际可用于唯一标识的位数为122位。

优点

简单,无需进行初始设置或使用集中式系统来管理ID。分布式系统中的每个服务都可以推出自己的唯一ID,无需交流。

缺点

128位长度较长,不容易写下或记住。UUID不会透露太多关于自身的信息。UUID不可排序(除了版本1和2)。2. NanoID(126位)

NanoID从UUID的概念中提取出一部分,使用了只包含64个字符的字母表(包括连字符和下划线),从而将事情简化了一些,但只需21个字符。

进行一下计算,每个NanoID字符占用6位,而不是UUID的4位,快速乘法后,我们可以得到NanoID的长度为整齐的126位。

NUp3FRBx-27u1kf1rmOxnXytMg-01fzdSaHoKXnPMJ_4hP-0rh8pNbx6-Qw1pMl

“在数据库中存储NanoID和UUID有很大的区别吗?”

嗯,如果将它们保存为字符串,NanoID可能会更高效一些,因为它比UUID短15个字符,但在二进制形式下,两者之间的差异只有2位,这在大多数存储情况下通常是一个细节。

优点

NanoID使用字符(A-Za-z0–9_-),对URL友好。只需21个字符,比UUID更紧凑,精确地减少了15个字符(尽管它是126位,而UUID是128位)。

缺点

NanoID是较新的技术,可能没有像UUID那样广泛支持。3. 序列号

序列号或自增可能会让人想起,因为像PostgreSQL和MySQL这样的数据库通常使用这种方法。

在其核心中,有一个递增的集中式计数器,但想象一下同时出现数百万个请求的情况。这个中心点将成为瓶颈和潜在的单点故障。

“那又怎样?我们不能分散负载吗?”

当然可以。每个节点可以拥有自己的ID生成器,按照递增的方式生成ID:

节点A:10 20 30 40节点B:1 11 21 31 41节点C:2 12 22 32 42// 或者节点A:a_1 a_2 a_3 a_4节点B:b_1 b_2 b_3 b_4

但是,排序变得有点棘手。

​虽然系统是分布式的,但每个节点仍然可能成为瓶颈。如果某个节点被无数个请求压垮,它将一个接一个地处理它们。

优点

这是一种直接的方法,额外的好处是ID可以排序,非常适合中小型系统。

缺点

在突发的大量请求增加时性能不佳。在分散式系统中移除节点可能会使问题复杂化。分散模型的ID不遵循全局顺序,使排序变得复杂。4. ObjectID (96 bits)

ObjectID是MongoDB为唯一文档ID提供的解决方案,这个由12字节组成的标识符通常存储在文档的“_id”字段中。如果您没有自己设置它,MongoDB会自动为您设置。

下面是构成ObjectID的内容:

时间戳(4字节):表示对象创建的时间,从Unix纪元开始计算(对于可能需要回顾的人来说,这是从1970年开始的时间戳)。随机值(5字节):每个机器或进程都有自己的随机值。计数器(3字节):给定机器的简单递增计数器。

“但是每个进程如何确保其随机值是唯一的呢?”

由于有5字节,我们可以得到2⁴⁰个潜在值,考虑到机器或进程的数量有限,冲突非常罕见。

在表示ObjectID时,MongoDB采用十六进制,将这12字节(或96位)转换为24个字符。

6502b4ab cf09f864b0 0748586502b4ab cf09f864b0 0748596502b4ab cf09f864b0 07485a

对于熟悉Go语言的人来说,以下是其实现的一瞥:

var objectIDCounter = readRandomUint32()var processUnique = processUniqueBytes()func NewObjectIDFromTimestamp(timestamp time.Time) ObjectID { var b [12]byte binary.BigEndian.PutUint32(b[0:4], uint32(timestamp.Unix())) copy(b[4:9], processUnique[:]) putUint24(b[9:12], atomic.AddUint32(&objectIDCounter, 1)) return b}

优点

无需集中机构监督唯一性,确保全局顺序。从字节大小上看,比UUID和NanoID更紧凑。使用ID进行排序很简单,您可以轻松查看每个对象的创建时间。显示创建项的特定进程或机器。由于其基于时间的结构,可以优雅地扩展,确保不会产生未来冲突。

缺点

尽管相对紧凑,但96位仍可被视为较长。在与客户端共享ObjectID时要小心,可能会泄露过多信息。5. Twitter Snowflake(64位)

Twitter常用的“Snowflake ID”系统是为了高效生成其庞大用户群的ID而开发的。

Snowflake ID实际上是一个64位整数,比MongoDB的ObjectID更紧凑。

符号位(1位):通常未使用,但可以保留用于特定功能。时间戳(41位):与ObjectID类似,表示数据创建时间(以毫秒为单位),跨越了约70年的时间。数据中心ID(5位):标识物理数据中心的位置。使用5位,我们最多可以有2⁵ = 32个数据中心。机器/进程ID(5位):与创建数据的单个机器、服务或进程相关联。序列号(12位):在每毫秒重置为0的递增计数器。

“等等。70年?所以从1970年开始,到2040年就结束了?”

没错!

许多Snowflake实现使用最近开始的自定义纪元,例如2010年11月4日01:42:54 UTC。至于它的优点,由于设计得当,这些优点非常明显。

您可以查看Go语言中的实现,使用的是bwmarrin/snowflake​:

func (n *Node) Generate() ID{ n.mu.Lock() now := time.Since(n.epoch).Nanoseconds() / 1000000 if now == n.time { n.step = (n.step + 1) & n.stepMask if n.step == 0 { for now <= n.time { now = time.Since(n.epoch).Nanoseconds() / 1000000 } } } else { n.step = 0 } n.time = now r := ID((now)<<n.timeShift | (n.node << n.nodeShift) | (n.step), ) n.mu.Unlock() return r}

缺点

对于中型企业来说,可能过于复杂,特别是对于具有多个数据中心、毫秒级时间戳、序列重置等复杂设置。寿命有限,大约为70年。

它具有一些某些人可能认为过多的功能,但对于像Twitter这样的巨头来说,它正好合适。

6. Sonyflake(64位)

受到Snowflake的启发,Sonyflake在位分配上进行了一些改动:

符号位(1位)时间戳(39位):Sonyflake以10毫秒为单位运行,将持续覆盖时间从Snowflake的约70年扩展到约174年。机器/进程ID(16位)序列号(8位):每10毫秒允许256个ID,这比Snowflake略慢,增加了高峰时段ID重叠的机会。​

根据其规格,Sonyflake似乎更适合中小型系统,对于极速度和规模不重要的情况。

掌握Golang精髓,释放编程潜能!关注我的《Golang实用技巧》专栏,它将为你揭秘生产环境最佳实践,带你探索高并发编程的实用教程。从分享实用的Golang小技巧到深入剖析实际应用场景,让你成为真正的Golang大师。无论你是初学者还是经验丰富的开发者,这里都有你所需要的灵感和知识。让我们一同探索Golang的无限可能!

0 阅读:1

超级欧派课程

简介:感谢大家的关注