聊聊分布式ID

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 聊聊分布式ID


大家好,我是G哥。本文来自苏三兄弟。

在复杂的分布式系统中,往往需要对大量的数据和消息进行唯一标识,例如:分库分表的 ID 主键、分布式追踪的请求 ID 等等。

于是,设计「分布式 ID 发号器」就成为了一个非常常见的系统设计问题。今天我将带大家一起学习一下,如何设计一个分布式 ID 发号器。

文章思维导图

系统诉求

对于业务系统而言,对于全局唯一 ID 一般有如下几个需求:

  1. 全局唯一。生成的 ID 不能重复,这是最基本的要求,否则在分库分表的场景下就会造成主键冲突。
  2. 单调递增。保证下一个 ID 大于上一个 ID,这样可以保证写入数据库的时候是顺序写入,提高写入性能。

对于上面两个需求来说,第一点是所有系统都要求的。而第二点则并不是所有系统都需要,例如分布式追踪的请求 ID 就可以不需要单调递增。而那些需要存到数据库里作为 ID 逐渐的场景,可能就需要保证全局唯一 ID 是单调递增的。

此外,我们可能还需要考虑安全方面的问题。如果一个全局唯一 ID 是顺序递增的,那么有可能会造成业务信息的泄露。例如订单 ID 每次递增 1,那么竞争对手直接通过订单 ID 就可以知道我们每天的订单数,这对于业务来说是不可接受的。

对于上述的诉求,现在市面上有非常多的唯一 ID 解决方案,其中最为常见的方案有如下 4 种:

  1. UUID
  2. 类雪花算法
  3. 数据库自增主键
  4. Redis 原子自增

UUID

UUID 全称叫 Universally Unique Identifier,即全局唯一标识符,它是 Java 中自带的 API。 一个标准的 UUID 包含 32 个 16 进制的数字,以中横线作为分隔符分为 5 段,每段的长度分别为 8 字符、4 字符、4 字符、4 字符、12 字符,大小为 36 个字符,如下图所示。一个简单的 UUID 示例:630e4100-e29b-33d4-a635-246652140000。

UUID 构成示意图

对于 UUID 这种唯一 ID 解决方案,优点是没有外部依赖,纯本地生成,因此其性能非常高。但缺点也是非常明显的:

  1. 字段非常长,浪费存储空间。 UUID 一般长度为 36 个字符串,如果作为数据库主键存储,极大地增加索引的存储空间。
  2. 非自增,降低数据库写入性能。 UUID 不是自增的,如果作为数据库主键,那么无法实现顺序写,从而会降低数据库写入性能。
  3. 没有业务含义。 UUID 是没有业务含义的,我们无法从 UUID 中获取到任何含义。

因此,对于 UUID 而言,其比较适用于非数据库 ID 存储的情况,例如生成一个本地的分布式追踪请求 ID。

类雪花算法

雪花算法(SnowFlake)是 Twitter 开源的分布式 ID 生成算法,其思路是用 64 位来表示一个 ID,并将 64 位分割成 4 个部分,如下图所示。

雪花算法唯一 ID 构成示意图

  • 第一个部分:1 位。 固定为 0,表示为正整数。二进制中最高位是符号位,1 表示负数,0 表示正数。ID 都是正整数,所以固定为 0。
  • 第二个部分:41 位。 表示时间戳,精确到毫秒,可以使用 69 年。时间戳带有自增属性。
  • 第三个部分:10 位。 表示 10 位的机器标识,最多支持 1024 个节点。此部分也可拆分成 5 位 datacenterId 和 5 位 workerId,datacenterId 表示机房 ID,workerId 表示机器 ID。
  • 第四部分:12 位。 表示序列化,即一些列的自增 ID,可以支持同一节点同一毫秒生成最多 4095 个 ID 序号。

雪花算法的优点是:

  1. 有业务含义,并且可自定义。 雪花算法的 ID 每一位都有特殊的含义,我们从 ID 的不同位数就可以推断出对应的含义。此外,我们还可根据自身需要,自行增删每个部分的位数,从而实现自定义的雪花算法。
  2. ID 单调增加,有利于提高写入性能。 雪花算法的 ID 最后部分是递增的序列号,因此其生成的 ID 是递增的,将其作为数据库主键 ID 时可以实现顺序写入,从而提高写入性能。
  3. 不依赖第三方系统。 雪花算法的生成方式,不依赖第三方系统或中间件,因此其稳定性较高。
  4. 解决了安全问题。 雪花算法生成的 ID 是单调递增的,但其递增步长又不是确定的,因此无法从 ID 的差值推断出生成的数量,从而可以保护业务隐私。

雪花算法几乎可以是非常完美了,但它有一个致命的缺点 —— 强依赖机器时间。 如果机器上的系统时间回拨,即时间较正常的时间慢,那么就可能会出现发号重复的情况。

对于这种情况,我们可以在本地维护一个文件,写入上次的时间戳,随后与当前时间戳比较。如果当前时间戳小于上次时间戳,说明系统时间出了问题,应该及时处理。

整体而言,雪花算法不仅长度更短,而且还具有业务含义,在数据库存储的场景下还能提高写入性能,因此雪花算法生成分布式唯一 ID 受到了大家的欢迎。

现在许多国内大厂的开源发号器的实现,都是在雪花算法的基础上做改进,例如:百度开源的 UidGenerator、美团开源的 Leaf 等等。这些类雪花算法的核心都是将 64 位进行更合理的划分,从而使得其更适合自身场景。

数据库自增主键

说起唯一 ID,我们自然会想起数据库的自增主键,因为它就是唯一的。

对于并发量低的情况下,我们可以直接部署 1 台机器,每次获取 ID 的时候就往数据库表插入一条数据,随后返回主键 ID。

这种方式的好处是非常简单,实现成本低。此外,生成的唯一 ID 也是单调自增的,可以满足数据库写入性能的要求。

但其缺点也非常明显,即其强依赖数据库。当数据库异常的时候,会造成整个系统不可用。即使做了高可用切换,主从切换时数据同步不一致时,仍然可能造成重复发号。

另外,由于是单机部署,因此其性能瓶颈限制在单台 MySQL 机器的读写性能上,注定无法承担起高并发的业务场景。

对于上面说到的性能问题,我们可以通过集群部署来解决。而集群部署之后的 ID 冲突问题,我们可以通过设置递增步长来解决。例如如果我们有 3 台机器,那么我们就设置递增步长为 3,每台机器的 ID 生成策略为:

  • 第 1 台机器,从 0 开始递增,步长为 3,生成的 ID 分别是:0、3、6、9 等等。
  • 第 2 台机器,从 1 开始递增,步长为 3,生成的 ID 分别是:1、4、7、10 等等。
  • 第 3 台机器,从 2 开始递增,步长为 3,生成的 ID 分别是:2、5、8、11 等等。

这种方式解决了集群部署以及 ID 冲突的问题,可以在一定程度上提升并发访问的容量。但其缺点也比较明显:

  1. 只能依赖堆机器提高性能。 当请求再次增多时,我们只能无限堆机器,这貌似是一种物理防御一样。
  2. 水平扩展困难。 当我们需要增加一台机器时,其处理过程非常麻烦。首先,我们需要先把新增的服务器部署好,设置新的步长,起始值要设置一个不可能达到的值。当把新增的服务器部署好之后,再一台台处理旧的服务器,这个过程真的非常痛苦,可以说是人肉运维了。

Redis 原子自增

由于 Redis 是内存数据库,其强大的性能非常适合用来实现高并发的分布式 ID 生成。基于 Redis 实现自增 ID,其主要还是利用了 Redis 中的 INCR 命令。该命令可以将某个数自增一并返回结果,并且这个操作是原子操作。

通过 Redis 实现分布式 ID 功能,其模式与通过数据库自增 ID 类似,只是存储介质从硬盘变成了内存。当单台 Redis 无法支撑并发请求的时候,Redis 同样可以通过集群部署和设置步长的方式去解决。

但数据库自增主键有的问题,Redis 自增 ID 的方式也同样会有,即只能堆机器,同时水平扩展困难。此外,比起数据库存储的持久化,Redis 是基于内存的存储,需要考虑持久化的问题,这同样是一个头疼的问题。

总结

看了这么多个分布式 ID 的解决方案,那么我们到底应该选哪个呢?

当我们在决策的时候,我们应该确定决策的维度。对于这个问题,我们应该关注的维度大致有:研发成本、并发量、性能、运维成本。

首先,对于 UUID 而言,其在各个方面其实都不如雪花算法,唯一的优点是 JDK 自带 API。因此,如果你只是极其简单地使用,那么就直接使用 UUID 就可以,毕竟雪花算法还得写一写实现代码呢。

其次,对于类雪花算法而言,其毋庸置疑是非常好的一种实现。与 UUID 相比,其不仅有 UUID 本地生成、不依赖第三方系统的优点,还有业务含义、能提高写入性能、解决了安全问题。但其缺点在于要实现雪花算法的代码,因此其研发成本稍稍比 UUID 高一些。

最后,对于数据库自增 ID 与 Redis 原子自增这两种方式。数据库自增 ID 的方式,其优点同样在于简单方便,不需要太高的研发成本。但其缺点是支撑的并发量太低,并且后续运维成本太高。因此,数据库自增 ID 这种方式,应该适用于小规模的使用场景下。而 Redis 原子自增的方式,其优先在于能支撑高并发的场景。但缺点是需要自行处理持久化问题,运维成本可能比较高。

本人更倾向于数据库自增方式。这两种方式都是非常类似的,唯一的区别是存储介质。Redis 原子自增方式非常快,可能单机可以是数据库方式的好几倍。但是如果要考虑持久化的问题,那对于 Redis 来说就太复杂了。

我们把上面这四种实现方式整理一下,可以汇总成下面的对比表格:

实现方案 优点 缺点 使用场景
UUID 性能高、不依赖第三方、研发成本低 字段长浪费存储空间、ID 不自增写入性能差、无业务含义 非常简单的使用场景(用于简单测试)
类雪花算法 有业务含义、单调递增写入性能好、不依赖第三方、业务安全 强依赖机器时间 高并发、业务场景复杂、需要将 ID 暴露给外部系统
数据库自增 ID 研发成本低、单调递增写入性能好 依赖数据库、只能堆机器提高性能、维护成本高 对持久性有要求,不暴露给外部系统
Redis 原子自增 高并发、单调递增写入性能好 依赖 Redis、有业务问题、有持久性问题 对持久性没要求,不暴露给外部系统

总的来说,如果站在长期使用考虑,那么运维成本、高并发肯定是需要考虑的。在这个基础条件下,类雪花算法与数据库自增 ID 或许是相对好的选择。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
缓存 算法 NoSQL
【分布式详解】一致性算法、全局唯一ID、分布式锁、分布式事务、 分布式缓存、分布式任务、分布式会话
分布式系统通过副本控制协议,使得从系统外部读取系统内部各个副本的数据在一定的约束条件下相同,称之为副本一致性(consistency)。副本一致性是针对分布式系统而言的,不是针对某一个副本而言。强一致性(strong consistency):任何时刻任何用户或节点都可以读到最近一次成功更新的副本数据。强一致性是程度最高的一致性要求,也是实践中最难以实现的一致性。单调一致性(monotonic consistency):任何时刻,任何用户一旦读到某个数据在某次更新后的值,这个用户不会再读到比这个值更旧的值。
645 0
|
3月前
|
算法 Go
[go 面试] 雪花算法与分布式ID生成
[go 面试] 雪花算法与分布式ID生成
|
9天前
|
算法 关系型数据库 MySQL
分布式唯一ID生成:深入理解Snowflake算法在Go中的实现
在分布式系统中,确保每个节点生成的 ID 唯一且高效至关重要。Snowflake 算法由 Twitter 开发,通过 64 位 long 型数字生成全局唯一 ID,包括 1 位标识位、41 位时间戳、10 位机器 ID 和 12 位序列号。该算法具备全局唯一性、递增性、高可用性和高性能,适用于高并发场景,如电商促销时的大量订单生成。本文介绍了使用 Go 语言的 `bwmarrin/snowflake` 和 `sony/sonyflake` 库实现 Snowflake 算法的方法。
21 1
分布式唯一ID生成:深入理解Snowflake算法在Go中的实现
|
22天前
|
NoSQL 算法 关系型数据库
分布式 ID 详解 ( 5大分布式 ID 生成方案 )
本文详解分布式全局唯一ID及其5种实现方案,关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 ID 详解 ( 5大分布式 ID 生成方案 )
|
4月前
|
存储 NoSQL Java
通用快照方案问题之通过Sleuth进行耗时分析和链路优化如何解决
通用快照方案问题之通过Sleuth进行耗时分析和链路优化如何解决
45 0
|
4月前
|
消息中间件 Java Nacos
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
77 0
|
4月前
|
存储 算法 Java
分布式自增ID算法---雪花算法(SnowFlake)Java实现
分布式自增ID算法---雪花算法(SnowFlake)Java实现
287 0
|
5月前
|
存储 算法 Java
分布式唯一ID解决方案-雪花算法
分布式唯一ID解决方案-雪花算法
54 0
|
6月前
|
SQL 算法
基于若依的ruoyi-nbcio流程管理系统修改代码生成的sql菜单id修改成递增id(谨慎修改,大并发分布式有弊端)
基于若依的ruoyi-nbcio流程管理系统修改代码生成的sql菜单id修改成递增id(谨慎修改,大并发分布式有弊端)
97 1
|
6月前
|
缓存 算法 关系型数据库
深度思考:雪花算法snowflake分布式id生成原理详解
雪花算法snowflake是一种优秀的分布式ID生成方案,其优点突出:它能生成全局唯一且递增的ID,确保了数据的一致性和准确性;同时,该算法灵活性强,可自定义各部分bit位,满足不同业务场景的需求;此外,雪花算法生成ID的速度快,效率高,能有效应对高并发场景,是分布式系统中不可或缺的组件。
1653 2
深度思考:雪花算法snowflake分布式id生成原理详解

热门文章

最新文章

下一篇
无影云桌面