分布式ID的生成方案

本文详细介绍了分布式系统中全局唯一ID的重要性和要求,包括唯一性、有序性、可用性、自主性和安全性。讨论了数据库自增ID、UUID以及snowflake雪花算法三种常见生成方法的优缺点和优化方案。数据库自增ID面临并发性和数据库压力问题,UUID虽然本地生成但过长且无序,而雪花算法则依赖时间戳和节点标识,易于扩展但存在时钟回拨风险。各种方法各有适用场景,需根据实际业务需求选择。
摘要由CSDN通过智能技术生成

在业务开发中,大量场景需要唯一 ID 来进行标识:用户需要唯一身份标识、商品需要唯一标识、消息需要唯一标识、事件需要唯一标识等,都需要全局唯一ID,尤其是复杂的分布式业务场景中全局唯一 ID 更为重要

 

分布式唯一ID 有哪些特性或要求呢

  • 唯一性:生成的 ID 全局唯一,在特定范围内冲突概率极小
  • 有序性:生成的 ID 按某种规则有序,便于数据库插入及排序
  • 可用性:可保证高并发下的可用性, 确保任何时候都能正确的生成 ID
  • 自主性:分布式环境下不依赖中心认证即可自行生成 ID
  • 安全性:不暴露系统和业务的信息, 如:订单数,用户数等

 

分布式唯一ID有哪些生成方法呢?

总的来说,大概有三大类方法,分别是:数据库自增 ID、UUID 生成、snowflake雪花算法

 

数据库自增ID

核心思想:使用数据库的 id 自增策略(如: MySQL 的 auto_increment

优点

  • 简单,天然有序

缺点

  • 并发性不好
  • 数据库写压力大
  • 数据库故障后不可使用
  • 存在数量泄露风险

针对以上缺点,有以下几种优化方案:

1、数据库水平拆分,设置不同的初始值和相同的自增步长

核心思想:将数据库进行水平拆分,每个数据库设置不同的初始值和相同的自增步长

如图所示,可保证每台数据库生成的 ID 是不冲突的,但这种固定步长的方式也会带来扩容的问题,很容易想到当扩容时会出现无 ID 初始值可分的窘境

解决方案有:

  • 根据扩容考虑决定步长
  • 增加其他位标记区分扩容

这其实都是在需求与方案间的权衡,根据需求来选择最适合的方式

 

2、批量缓存自增 ID

核心思想:如果使用单台机器做 ID 生成,可以避免固定步长带来的扩容问题(方案 1 的缺点)

具体做法是:每次批量生成一批 ID 给不同的机器去慢慢消费,这样数据库的压力也会减小到 N 分之一,且故障后可坚持一段时间

如图所示,但这种做法的缺点是服务器重启、单点故障会造成 ID 不连续

 

3、Redis生成ID

核心思想:Redis 的所有命令操作都是单线程的,本身提供像 incr 和 increby这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的

优点

  • 不依赖于数据库,灵活方便,且性能优于数据库
  • 数字 ID 天然排序,对分页或者需要排序的结果很有帮助

缺点

  • 如果系统中没有 Redis,还需要引入新的组件,增加系统复杂度
  • 需要编码和配置的工作量比较大

优化方案:
考虑到单节点的性能瓶颈,可以使用 Redis 集群来获取更高的吞吐量,并利用上面的方案(①数据库水平拆分,设置不同的初始值和相同的步长; ②批量缓存自增 ID)来配置集群

注意: 比较适合使用 Redis 来生成每天从 0 开始的流水号。比如:“订单号=日期+当日自增长号”,则可以每天在 Redis 中生成一个 Key,使用 INCR 进行累加

 

UUID生成

核心思想:结合机器的网卡(基于名字空间/名字的散列值 MD5/SHA1)、当地时间(基于时间戳&时钟序列)、一个随机数来生成 UUID

其结构如下:
aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee(即包含 32 个 16 进制数字,以连字号-分为五段,最终形成“8-4-4-4-12”的 36 个字符的字符串,即 32 个英数字母+4 个连字号)
例如:550e8400-e29b-41d4-a716-446655440000

优点

  • 本地生成,没有网络消耗,生成简单,没有高可用风险

缺点

  • 不易于存储:UUID 太长,16 字节 128 位,通常以 36 长度的字符串表示,很多场景不适用
  • 信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置
  • 无序查询效率低:由于生成的 UUID 是无序不可读的字符串,所以其查询效率低

 

到目前为止业界一共有 5 种方式生成 UUID:

版本 1 - 基于时间的 UUID(date-time & MAC address):
规则:主要依赖当前的时间戳及机器 mac 地址,因此可以保证全球唯一性
优点:能基本保证全球唯一性
缺点:使用了 Mac 地址,因此会暴露 Mac 地址和生成时间

版本 2 - 分布式安全的 UUID(date-time & group/user id):
规则:将版本 1 的时间戳前四位换为 POSIX 的 UID 或 GID,很少使用
优点:能保证全球唯一性
缺点:很少使用,常用库基本没有实现

版本 3 - 基于名字空间的 UUID-MD5 版(MD5 hash & namespace):
规则:基于指定的名字空间/名字生成 MD5 散列值得到,标准不推荐
优点:不同名字空间或名字下的 UUID 是唯一的;相同名字空间及名字下得到的UUID 保持重复
缺点:MD5 碰撞问题,只用于向后兼容,后续不再使用

④版本 4 - 基于随机数的 UUID(pseudo-random number):
规则:基于随机数或伪随机数生成
优点:实现简单
缺点:重复几率可计算。机率也与随机数产生器的质量有关。若要避免重复机率提高,必须要使用基于密码学上的强伪随机数产生器来生成值才行

⑤版本 5 - 基于名字空间的 UUID-SHA1 版(SHA-1 hash & namespace):
规则:将版本 3 的散列算法改为 SHA1
优点:不同名字空间或名字下的 UUID 是唯一的;相同名字空间及名字下得到的UUID 保持重复
缺点:SHA1 计算相对耗时

总得来说:

  • 版本 1/2 适用于需要高度唯一性且无需重复的场景
  • 版本 3/5 适用于一定范围内唯一且需要或可能会重复生成 UUID 的环境下
  • 版本 4 适用于对唯一性要求不太严格且追求简单的场景

 

雪花算法

核心思想:把 64-bit 分别划分成多段,分开来标示机器、时间、某一并发序列等,从而使每台机器及同一机器生成的 ID 都是互不相同

这种结构是雪花算法提出者 Twitter 的分法,但实际上这种算法使用可以很灵活,根据自身业务的并发情况、机器分布、使用年限等,可以自由地重新决定各部分的位数,从而增加或减少某部分的量级。比如:百度的 UidGenerator、美团的 Leaf 等,都是基于雪花算法做一些适合自身业务的变化

下面介绍雪花算法的几种不同优化方案:

1、Twitter 的 snowflake 算法

核心思想是:采用 bigint(64bit)作为 id 生成类型,并将所占的 64bit 划分成多段

其结构如下:

1 位标识:由于 long 基本类型在 Java 中是带符号的,最高位是符号位,正数是 0,负数是 1,所以 id 一般是正数,最高位是 0

41 位时间截(毫秒级):需要注意的是,41 位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)得到的值,这里的开始时间截,一般是指我们的 id 生成器开始使用的时间截,由我们的程序来指定
41 位的毫秒时间截,可以使用 69 年(即 T =(1L << 41)/(1000 * 60 * 60 *24 * 365)= 69)

10 位的数据机器位:包括 5 位数据中心标识 Id(datacenterId)、5 位机器标识 Id(workerId),最多可以部署 1024 个节点(即 1 << 10 = 1024)。超过这个数量,生成的 ID 就有可能会冲突

12 位序列:毫秒内的计数,12 位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生 4096 个 ID 序号(即 1 << 12 = 4096)

PS:全部结构标识(1+41+10+12=64)加起来刚好 64 位,刚好凑成一个 Long 型

优点

  • 整体上按照时间按时间趋势递增,后续插入索引树的时候性能较好
  • 整个分布式系统内不会产生 ID 碰撞(由数据中心标识 ID、机器标识 ID 作区分)。
  • 本地生成,且不依赖数据库(或第三方组件),没有网络消耗,所以效率高(每秒能够产生 26 万 ID 左右)

缺点

  • 由于雪花算法是强依赖于时间的,在分布式环境下,如果发生时钟回拨,很可能会引起 ID 重复、ID 乱序、服务会处于不可用状态等问题

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值