如何在分布式场景下生成全局唯一 ID ?
全局唯一:这是最基本的要求,不能重复;
递增:有些特殊场景是必须递增的,比如事务版本号,后面生成的 ID 一定要大于前面的 ID;有些场景递增比不递增要好,因为递增有利于数据库索引的性能;
高可用:如果是生成唯一 ID 的系统或服务,那么一定会有大量的调用,那么保证其高可用就非常关键了;
信息安全:如果 ID 是连续的,那么很容易被恶意操作或泄密,比如订单号是连续的,那么很容易就被看出来一天的单量大概是多少;
另外考虑到存储压力,ID 当然是越短越好。
优点:理解起来最容易,实现起来也最简单。
缺点:也非常明显了,每种数据库的实现不同,如果数据库需要迁移的话比较麻烦;最大的问题是性能问题,并发量到一定级别的时候这个方法估计会很难满足性能需求;另外通过数据库自增生成的 ID 携带的信息太少,只能起到一个标识的作用,同时自增 ID 也是连续的。
{"_id": ObjectId("5d47ca7528021724ac19f745")}
3.2 之前的版本(包括 3.2):4 字节时间戳 + 3 字节机器标识符 + 2 字节进程 ID + 3字节随机计数器
3.2 之后版本:4 字节时间戳 + 5 字节随机值 + 3 字节递增计数器
优点:性能高于数据库;可以使用集群部署;ID 内自带一些含义,比如时间戳;
缺点:和数据库一样,需要引入对应的组件/软件,增加了系统的复杂度;最关键的是,这两种方案都意味着生成全局唯一 ID 的系统(服务),会成为一个单点,在软件架构中,单独就意味着风险;如果这个服务出现问题,那么所有依赖于这个服务的系统都会崩溃掉。
Version 2:DCE 安全的 UUID,把 Version 1 中的时间戳前 4 位置换为 POSIX 的 UID 或 GID ;高度唯一。
Version 3:基于名字的 UUID(MD5),通过计算名字和名字空间的 MD5 散列值得到;一定范围内唯一。
Version 4: 随机 UUID,根据随机数或伪随机数生成 UUID;有一定概率重复。
Version 5:基于名字的UUID(SHA1),和 Version 3 类似,只是散列值计算使用SHA1算法;一定范围内唯一。
public class CreateUUID {
public static void main(String[] args) {
String uuid = UUID.randomUUID().toString();
uuid = UUID.randomUUID().toString().replaceAll("-","");
System.out.println("uuid : " + uuid);
}
}
优点:本地生成,没有网络消耗,不需要第三方组件(也就没有单点的风险),生成比较简单,性能好。
缺点:长度长,不利于存储,并且没有排序,相对来说还会影响性能(比如 MySQL 的 InnoDB 引擎,如果 UUID 作为数据库主键,其无序性会导致数据位置频繁变动)。
1 bit :不使用,固定是 0;
41 bit :时间戳(毫秒),数值范围是:0 至 2的41次方 - 1;转换成年的话,大约是 69 年;
10 bit :机器 ID ;5 位机房 ID + 5 位机器 ID;(服务集群数量比较小的时候,可以手动配置,服务规模大的话,可以采用第三方组件进行自动配置,比如美团的 Leaf-snowflake,就是通过 ZooKeeper 的持久顺序节点做为机器 ID)
12 bit : 序列号,用来记录同一个毫秒内生成的不同 ID。
优点:本地生成,没有网络消耗,不需要第三方组件(也就没有单点的风险),一定范围内唯一(基本可以满足大部分场景),性能好,按时间戳递增(趋势递增);
缺点:依赖于机器时钟,同一台机器如果把时间回拨,生成的 ID 就会有重复的风险。
@Resource
private UidGenerator uidGenerator;
@Test
public void testSerialGenerate() {
// Generate UID
long uid = uidGenerator.getUID(); System.out.println(uidGenerator.parseUID(uid));
}