userId生成的策略类型

目前常见的userId生成策略主要有:

  • 数据库自增id
  • UUID
  • 雪花算法

数据库自增id

简单明了,使用数据库中的主键自增即可实现

优点在于:

  • 自动自增,可以作为索引提升数据库的查询效率
  • 节省磁盘空间,相较于其他两种策略可以节省大量空间
  • 查询、写入效率高

缺点在于:

  • 在导入数据时可能存在id重复的问题
  • 不适应与分布式架构,在该中存在id重复的问题
  • 无法进行分表,拆表等操作,与分布式的缺陷类似

UUID

UUID(Universally Unique Identifier):它是由一组32个十六进制数字组成的字符串,总共分为无端,每段之间用连字符(-)隔开,包括连字符在内共36个字符

1
2
3
// 生成UUID
String uuid = UUID.randomUUID.toString()

UUID基于硬件地址(MAC地址)、时间戳和随机因子来生成Id

优势在于:

  • 几乎不可能重复,可以用于分布式系统
  • 具有唯一性、高性能和高可用的特点
  • 本地生成

缺点在于:

  • 产生的值较长,足足有36个字符
  • ID完全随机,没有任何顺序可言
  • 可读性差,且ID不具有任何意义
  • 字符串类型读写效率差

雪花算法(Snowflake)生成ID

Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java64bit的整数是Long类型,所以在 JavaSnowFlake 算法生成的 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-bitIDC,分5-bit给工作机器。这样就可以表示32IDC,每个IDC下可以有32台机器,具体的划分可以根据自身需求定义。
  • 最后12-bit位是自增序列,可表示2^12 = 4096个数。

这样的划分之后相当于在一毫秒一个数据中心的一台机器上可产生4096个有序的不重复的ID。但是我们 IDC 和机器数肯定不止一个,所以毫秒内能生成的有序ID数是翻倍的。

优点在于:

  • 生成的ID全局唯一
  • 分布式算法,可以在多台机器上生成ID
  • 生成的ID64位整数,Long类型,读写效率高

缺点在于:

  • 依赖机器时钟:雪花算法使用了时间戳作为生成ID的一部分,因此对于分布式系统来说,各个机器的时钟需要保持同步,否则可能会导致生成重复的ID
  • 有限的容量:雪花算法中的机器ID和序列号都是固定长度的,因此在极端情况下,可能会达到容量上限,无法继续生成唯一的ID
  • 不适合特定场景(安全):雪花算法生成的ID是递增的,如果对于一些需要保密性的场景,可能会暴露一些敏感信息,例如系统的使用频率和规模。

改进的方法:百度 UidGenerator 和 美团分布式ID生成系统 Leafsnowflake模式都是在 snowflake 的基础上演进出来的

关于 百度-UidGenerator

UidGeneratorJava实现的, 基于Snowflake算法的唯一ID生成器
UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。
在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// SpringBoot整合UidGenerator
@Configuration
public class UidConfig {
@Bean(name = "defaultUidGenerator")
public DefaultUidGenerator defaultUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
DefaultUidGenerator uidGenerator = new DefaultUidGenerator();
uidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
uidGenerator.setTimeBits(29);
uidGenerator.setWorkerBits(21);
uidGenerator.setSeqBits(13);
uidGenerator.setEpochStr("2016-09-20");
return uidGenerator;
}

@Bean(name = "cachedUidGenerator")
public CachedUidGenerator cachedUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
CachedUidGenerator uidGenerator = new CachedUidGenerator();
uidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
uidGenerator.setTimeBits(29);
uidGenerator.setWorkerBits(21);
uidGenerator.setSeqBits(13);
uidGenerator.setEpochStr("2016-09-20");
uidGenerator.setBoostPower(3);
uidGenerator.setPaddingFactor(50);
uidGenerator.setScheduleInterval(60);
// uidGenerator.setRejectedPutBufferHandler(...); // 根据需求设置拒绝策略
// uidGenerator.setRejectedTakeBufferHandler(...); // 根据需求设置拒绝策略
return uidGenerator;
}

@Bean
public DisposableWorkerIdAssigner disposableWorkerIdAssigner() {
return new DisposableWorkerIdAssigner();
}
}

本文参考: