概述
在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。比如MySQL一般都是采用分库分表的架构,那么我们就需要保证所有数据的ID全局唯一,而不仅仅是在单个MySQL数据库表中唯一。此时一个能生成唯一ID的方案是非常必要的。而此时对于生成的全局ID,我们要考虑方案的几个方面:
- 全局唯一
- 递增性:由于数据是存储在MySQL上的,MySQL索引以B+树的形式组织,递增的ID可以提高MySQL的性能
- 信息安全:外界是否能通过ID判断出系统的一些信息,防止通过ID进行一些恶意的操作
- 性能:在如今高并发的环境下,我们需要考虑生成ID的性能
而目前业界生成分布式唯一ID的方案大概有以下几种
- UUID
- 数据库发号
- 主键自增发号
- 分段发号
- 随机字符串/随机数
- Snowflake算法
UUID
最常见的ID生成方案,简单的来说,UUID可以让服务器在不需要任何外界依赖的情况下,基于当前时间、硬件标识等等信息生成的唯一ID。一般表现为16字节的字符串。
优点:
- 无任何依赖,无需中心化的服务器
- 实现简单
- 生成的UUID没有任何可用信息,保护系统信息安全
缺点:
- 生成的UUID没有任何可用信息,可读性差
- 占用空间太多
- UUID字符串,不递增,不适合作为数据库主键,会影响数据库性能
场景:通常可以作为一些临时性唯一标识,例如用户登陆后,生成一个UUID作为登录的SessionID,作为key存储在Redis中,Value是用户相关的信息。
数据库发号
我们可以通过单点数据库发号来实现
根据主键自增发号
直接通过主键自增连续发号,有单机和多机两种方式。
单机数据库主键自增
通过操作数据库中的一张表,插入一条数据后或者该数据的主键ID,用主键ID作为全局的唯一ID
优点:
- 实现简单,方便接入,只依赖于数据库
- 完全单调递增,有利于数据库性能的提高
缺点:
- 性能较低,由于发号依赖于数据库插入数据,所以并发比较低
- 易出现单点故障,一旦单机数据库发生宕机,就没法生成ID,系统就无法使用
- ID连续,容易泄露信息,比如恶意刷API,比如通过ID推测出系统数据量级
场景:适用于并发量不高的业务
多数据库主键自增
对于多数据库的主键自增,我们要保证所有数据库都不重复,那么就可以给不同数据库设置不同的步长,可以让不同数据库生成的ID不同。
比如说有4台机器,那么就可以设置步长为4
- 1 、5、9、13、17
- 2、6、10、14、18
- 3、7、11、15、19
- 4、8、12、16、20
然后业务层通过Hash轮询不同的数据库来获取生成的ID,这样就通过多台数据库完成了发号操作。
在MySQL中设置步长的命令为
alter table <table name> auto_increment= 4;
优点:
- 相比单台数据库来说,性能更高,因为可以多台数据库同时发号
缺点:
- 实现比较复杂,依赖多台数据库
- 不方便扩容
场景:在没有分库分表的框架以前,业务上的分库分表就是使用这种方案来实现的。
分段发号
简单来说,就是用一个数据库表充当发号器,但是并不是用主键自增,而是用一个单独的字段。
字段 | 意义 |
---|---|
tag | 用于区分每种Id应用的业务 |
max_id | 记录当前已生成的最大ID |
step | 每次可以获取Id的数量 |
description | 描述信息 |
update_time | 更新时间 |
每次使用下面这条语句从数据库获取step数量的id,并且更新max_id的值,将step数量的ID存储在内存中,供业务方通过HTTP,RPC,Client等方式来获取。此处由于多线程并发,所以存在内存中的ID应保证其操作具有线程安全性,比如在Java中应该使用原子类AtomicLong。
UPDATE leaf_alloc SET max_id = max_id + step WHERE tag = #{tag}
该方法也是美团的开源项目Leaf所采用的方式之一。
优点:
- 效率较高,生成的效率取决于Step的大小
缺点:
- 单点故障问题依旧存在
- ID连续,容易出现信息安全问题
场景:适用于系统仅有单台数据库但并发量较高的情况。
Snowflake算法
Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java中64bit的整数是Long类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。
- 第1位占用1bit,其值始终是0,可看做是符号位不使用。
- 第2位开始的41位是时间戳,41-bit位可表示2^41个数,每个数代表毫秒,那么雪花算法可用的时间年限是
(1L<<41)/(1000L360024*365)
=69 年的时间。 - 中间的10-bit位可表示机器数,即2^10 = 1024台机器,但是一般情况下我们不会部署这么台机器。如果我们对IDC(互联网数据中心)有需求,还可以将 10-bit 分 5-bit 给 IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,具体的划分可以根据自身需求定义。
- 最后12-bit位是自增序列,可表示2^12 = 4096个数。
这样的划分之后相当于在一毫秒一个数据中心的一台机器上可产生4096个有序的不重复的ID。但是我们 IDC 和机器数肯定不止一个,所以毫秒内能生成的有序ID数是翻倍的。
优点:
- 性能高
- ID不连续,保证了信息安全
- ID趋势递增,保证了数据库的性能
缺点:
- 一般类Snowflake算法会引入注册中心,增加了系统的复杂性
- 存在系统时钟回拨导致重复ID的问题,可以通过注册中心解决,保证全局唯一时钟
总结
分布式ID的生成在如今分布式环境下越来越重要了,读者也可以根据文章的几种方式,自己实现一些ID生成的Demo