分布式ID
1. 是什么?
分布式 ID 就是在分布式项目中我们给数据库记录用的 ID。和单机版项目有啥不同呢?单机版的我们可以用 数据库自增等方式来生成 ID,但是分布式项目中,项目部署在好几台机器上,数据库自增也是有可能会出现重复的情况。所以就需要一种算法来生成适用于分布式系统的 ID。
2. 生成分布式 ID 的算法要求:
- 全局唯一:生成的 ID 必须全局唯一;
- 趋势递增:我们应该尽量选择有序的主键来保证索引的性能;
- 单调递增:尽量保证下一个的 ID 大于上一个;
- 信息安全:如果是连续的 ID,攻击者很容易就猜出下一条记录的 ID,所以有些情况下尽量让 ID 无规则;
- 含时间戳:含时间戳便于追踪。
3. 生成分布式 ID 系统的可用性要求:
我们用一个系统来生成分布式 ID,那么这个系统必须符合以下条件:
- 高可用:要保证在99.999%的情况下能够生成一个唯一的分布式 ID;
- 低延迟:生成分布式 ID 的速度一定要快;
- 高QPS:假如一下子来10w个生成分布式 ID 的请求,服务器要能扛得住并且能够一下子生成10w个。
4. 分布式 ID 的生成方案:
-
UUID:包含32个16进制的数字,以连字符分割成五段,格式是8-4-4-4-12。优点是性能好,本地生成,没有网络消耗。缺点是它无序,不能生成递增的 ID,而且很长,入库性能差,因为 MySQL的 是 B+ 树索引,每插入一条新数据,都会对索引进行改造,因为 UUID 的无序,每次插入数据时 B+ 树的改造就会很大,也就是导致索引分裂。
-
数据库自增:我们可以专门搞个表,利用 MySQL 的replace into 语句来生成 ID。比如创建一个表:
create table t_test(
id bigint(20) unsigned not null auto_increment primary key,
col char(1) not null default '',
unique key col (col)
)
然后我们可以执行语句:
replace into t_test (col) values('a');
每执行一次,t_test 表的 id 字段值就会自增,我们就可以用这个 id 来做分布式 ID。这个方案对于并发量不高的系统足够了,但是并发量高的话还是不行的。首先用来生成 ID 的这个 MySQL 扛不住高并发,一秒钟生成10w个肯定是做不到的。
- 使用 redis 生成分布式 ID:因为 redis 的命令是原子操作的,所以可以使用 incr 和 incrby 来生成分布式 ID。但是这个方案也不是很完美,因为 redis 是集群的话,我们同样需要设置不同的自增步长,同时 key 要设置过期时间。集群有多少台,步长这么设置这些都是要考虑的。而且为了一个 ID 要部署和维护一套 redis 的集群,成本偏高。
所以以上三种方案都存在一定的缺点,现在比较流行的是用雪花算法。
5. 雪花算法:
雪花算法是推特开源的一套用于生成分布式 ID 的算法。它可以生成一个 64bit 大小的整数,类型是 Long,转成字符串后最长是19位。
(1). 雪花算法的组成:
0 0000000000 0000000000 0000000000 0000000000 0 0000000000 0000000000 00
1bit符号位 这 41 bit 是时间戳 10 bit 工作进程位 12bit 序列号位
1 + 41 + 10 + 12 的结构,总共 64bit,所以刚好可以对应 java 的 long 类型,所以雪花算法生成的 id 就用 long 类型存储。
- 符号位永远是0,0表示整,1表示负,我们生成的 id 肯定不希望是负的;
- 时间戳是41位,假如全都是1,那就是2的41次方减1,该值是毫秒,换算成年就是69.73年,所以说雪花算法可以用大约69年,从1970开始,可以用到2039年。
- 工作进程位是10bit,并且这10bit有5bit是机房号,5bit是机器编号,2的5次方是32,所以就支持32个机房,每个机房32台机器,总共就支持1024个节点。
- 序列号位是12bit,用来记录同毫秒内产生的不同 id。2的12次方减1是4095,表示同一机器在同一毫秒内可以生成4095个 ID。
(2). 怎么用?
hutool 工具包已经给我们封装好了,只需要在项目中引入:
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-captcha -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>5.6.6</version>
</dependency>
然后通过如下方式即可获取到雪花算法生成的 ID:
public class SnowFlakeUtil {
public static long snowFlakeId(long workerId, long datacenterId){
Snowflake snowflake = IdUtil.getSnowflake(workerId, datacenterId);
return snowflake.nextId();
}
public static void main(String[] args){
ExecutorService service = Executors.newFixedThreadPool(5);
// 机器的workerId
long workerId = 1;
// 机房ID
long datacenterId = 1;
for (int i = 0; i < 20; i++) {
service.submit(() ->{
System.out.println(snowFlakeId(workerId, datacenterId));
});
}
service.shutdown();
}
}
(3). 雪花算法优缺点:
优点是简单易用,有序递增,带时间戳,也满足信息安全。缺点也有,就是依赖机器时钟,可能会有时钟回拨问题。如果两台服务器的时间不同步,可能会导致生成重复的 ID。
(4). 雪花算法的优化:
百度开源的 UidGenerator 和美团开源的 Leaf 就解决了时钟回拨问题。