分布式ID

如何为不同的数据节点生成全局唯一主键呢?

  • 生成分布式 ID

最基本的分布式 ID 需要满足下面这些要求:

  • 全局唯一:ID 的全局唯一性肯定是首先要满足的!
  • 高性能:分布式 ID 的生成速度要快,对本地资源消耗要小。
  • 高可用:生成分布式 ID 的服务要保证可用性无限接近于 100%。
  • 方便易用:拿来即用,使用方便,快速接入!

一个比较好的分布式 ID 还应保证:

  • 安全:ID 中不包含敏感信息。
  • 有序递增:如果要把 ID 存放在数据库的话,ID 的有序性可以提升数据库写入速度。并且,很多时候,我们还很有可能会直接通过 ID 来进行排序。
  • 有具体的业务含义:生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过 ID 就能确定是哪个业务)。
  • 独立部署:也就是分布式系统单独有一个发号器服务,专门用来生成分布式 ID。这样生成 ID 的服务可以和业务相关的服务解耦
    • 不过,这样同样带来了网络调用消耗增加的问题。总的来说,如果需要用到分布式 ID 的场景比较多的话,独立部署的发号器服务还是很有必要的。

分布式 ID 常见解决方案

数据库

  • 数据库主键自增

    • 通过关系型数据库的自增主键产生唯一的 ID。
    • 通过 replace into 来插入数据
      • 尝试把数据插入到表中。
      • 如果主键或唯一索引字段出现重复数据错误而插入失败时,先从表中删除含有重复关键字值的冲突行,然后再次尝试把数据插入到表中。
    • 优缺点
      • 优点:实现起来比较简单、ID 有序递增、存储消耗空间小。
      • 缺点:支持的并发量不大、存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊!)、每次获取 ID 都要访问一次数据库(增加了对数据库的压力,获取速度也慢)。
  • 数据库号段模式

    • 可以批量获取,然后存储在内存里面,需要用到的时候,直接从内存里面拿就舒服了!这也就是我们说的基于数据库的号段模式来生成分布式 ID。
    • 数据库的号段模式也是目前比较主流的一种分布式 ID 生成方式。像滴滴开源的 Tinyid 就是基于这种方式来做的。不过,TinyId 使用了双号段缓存、增加多 db 支持等方式来进一步优化。
    • 相比于数据库主键自增的方式,数据库的号段模式对于数据库的访问次数更少,数据库压力更小。
    • 优缺点:
      • 优点:ID 有序递增、存储消耗空间小。
      • 缺点:存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊!)。
  • NoSQL

    • NoSQL 方案使用 Redis 多一些。我们通过 Redis 的 incr 命令即可实现对 ID 的原子顺序递增。
    • 为了提高可用性和并发,我们可以使用 Redis Cluster。Redis Cluster 是 Redis 官方提供的 Redis 集群解决方案。
    • Redis 方案的优缺点:
      • 优点:性能不错并且生成的 ID 是有序递增的。
      • 缺点:和数据库主键自增方案的缺点类似。
  • MongoDB ObjectId

    • MongoDB ObjectId 经常也会被拿来当做分布式 ID 的解决方案。
    • MongoDB ObjectId 一共需要 12 个字节存储:
      • 0~3:时间戳
      • 3~6:代表机器 ID
      • 7~8:机器进程 ID
      • 9~11:自增值
    • 优缺点
      • 优点:性能不错并且生成的 ID 是有序递增的。
      • 缺点:需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)、有安全性问题(ID 生成有规律性)。

算法

  • UUID

    • UUID 是 Universally Unique Identifier(通用唯一标识符)的缩写。UUID 包含 32 个 16 进制数字(8-4-4-4-12)。
    • image-20240811173525009
    • UUID 的优缺点
      • 优点:生成速度比较快、简单易用。
      • 缺点:存储消耗空间大(32 个字符串,128 位)、不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)。
  • Snowflake(雪花算法)

    • Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:
    • image-20240811173530723
    • sign (1bit): 符号位(标识正负),始终为 0,代表生成的 ID 为正数。
    • timestamp (41 bits): 一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2^41 毫秒(约 69 年)。
    • datacenter id + worker id (10 bits): 一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
    • sequence (12 bits): 一共 12 位,用来表示序列号。序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个唯一 ID。
    • 在实际项目中,我们一般也会对 Snowflake 算法进行改造,最常见的就是在 Snowflake 算法生成的 ID 中加入业务类型信息。
    • 优缺点:
      • 优点:生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)。
      • 缺点:需要解决重复 ID 问题(ID 生成依赖时间,在获取时间的时候,可能会出现时间回拨的问题,也就是服务器上的时间突然倒退到之前的时间,进而导致会产生重复 ID)、依赖机器 ID 对分布式环境不友好(当需要自动启停或增减机器时,固定的机器 ID 可能不够灵活)。