通常情况下,一个应用可以直接使用数据库的自增主键 ID 作为生成方案,这种做法既方便又通过数据库唯一约束避免了 ID 冲突的问题。但这种具有连续性的ID会暴露平台的数据量,并且在数据量过大进行分库分表后,就无法依赖数据库的自增 ID 作为生成方案了。如何在这种情况下生成一个全局唯一的ID呢?
UUID
UUID 是 Universally Unique Identifier 的缩写,由32个16进制数字 + 4个“-”构成,整体长度为36(标准的 UUID 格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),共32个字符)。其生成规则基于一定算法,目前共有5个版本,每个版本都有不同的生成方式,不同版本的 UUID 的唯一性指标不同。目前最常用的是版本1(通过时间戳的方式生成)和版本4(通过随机数的方式生成)。由于其长度和生成算法的原因,发生碰撞的概率极低,一般情况下默认其可以保证唯一性。不过需要注意的是版本4通过随机数的方式生成,也注定了其并不适合数据量大或并发较高的场景。
UUID 的优点:
- 实现简单,且生成性能较高
- 本地即可生成,不依赖任何中间件
UUID 的缺点:
- 长度过长,可读性差,存储成本高
- 生成的ID无序,影响数据库的写入性能(在MySQL中会导致页分裂;索引大小增加;新的UUID值通常会插入到叶子节点的中间位置,可能导致B+树的分裂和平衡操作频繁进行,从而增加了写入的开销)
在 Java 中 JDK 提供了 v3版本(基于名称空间MD5) UUID.nameUUIDFromBytes()
和 v4版本(基于随机数) UUID.randomUUID()
Snowflake
雪花算法(Snowflake)是Twitter公司开源的分布式ID生成算法,其生成的分布式ID共64位,由4个部分组成。
第一部分:1位。 固定为0,表示为正整数。二进制中最高位是符号位,ID为正整数,所以固定为0。
第二部分:41位。 表示精确到毫秒的时间戳,时间戳带有自增属性,可以使用69年。
第三部分:10位。 表示10位的机器标识,最多支持1024个节点。
第四部分:12 位。 表示自增序列,可以支持同一节点同一毫秒生成最多4096个ID。
优点:
- 高性能高可用,生成时不依赖中间件
- ID趋势自增
缺点:
- 时钟回拨后可能出现ID重复的问题
- 需要指定机器标识和数据中心ID,若配置错误可能导致ID重复
依赖第三方中间件
数据库自增
选择一个数据库作为中央数据库,利用该库中某表的自增主键机制生成分布式ID。单调递增,但在高并发访问数据库时存在阻塞问题,且存在数据库单点故障的问题。
以下是一个简单的中央数据库自增的例子:
1 | -- 创建ID表 |
号段模式
号段模式是在数据库的基础上为解决性能问题而产生的一种方案,每次生成ID时并非只取一个而是取一批,并将获取的号段以其他方式缓存,在号段用尽前都会从缓存中取ID。号段模式的好处是在同一个客户端中,生成的ID是顺序递增的,并且不需要频繁访问数据库。缺点是没办法保证全局顺序递增,也存在数据库的单点故障问题。
而为了防止多个实例之间发生冲突,需要采用号段的方式,即给每个客户端发放的时候按号段分开,如客户端A取的号段是1-1000,客户端B取的是1001-2000,客户端C取的是2001-3000。当客户端A用完之后,再来取的时候取到的是3001-4000。
Redis
既然基于磁盘的关系型数据库可能出现性能问题,那么也可以使用基于内存的 Redis 来避免该问题。由于 Redis 是单线程的,可以使用其提供的 incr
自增命令来生成分布式ID。
1 | 127.0.0.1:6379> set id_sequence 1234567890 // 将分布式ID初始化为1234567890 |
但需要注意的是,由于 Redis 是基于内存的,在持久化数据到磁盘前,即使是集群也无法避免断电丢失数据的问题。