数据库
1. 基于数据库自增ID
基于数据库的auto_increment自增ID完全可以充当分布式ID,具体实现:需要一个单独的MySQL实例用来生成ID,建表结构如下:
CREATE DATABASE `SEQ_ID`;
CREATE TABLE SEQID.SEQUENCE_ID (
id bigint(20) unsigned NOT NULL auto_increment,
value char(10) NOT NULL default '',
PRIMARY KEY (id),
);
insert into SEQUENCE_ID(value) VALUES ('values');
value 字段无意义,只是为了占位。
优点
实现简单,ID单调自增
缺点
强依赖DB,DB单点存在宕机风险,无法扛住高并发场景
2. 基于数据库的号段模式
数据库主键自增这种模式,每次获取 ID 都要访问一次数据库,ID 需求比较大的时候,肯定是不行的。 如果我们可以批量获取,然后存在在内存里面,需要用到的时候,直接从内存里面拿就舒服了!这也就是我们说的 基于数据库的号段模式来生成分布式 ID。 号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。表结构如下:
CREATE TABLE `sequence_id_generator` (
`id` int(10) NOT NULL,
`current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',
`step` int(10) NOT NULL COMMENT '号段的长度',
`version` int(20) NOT NULL COMMENT '版本号',
`biz_type` int(20) NOT NULL COMMENT '业务类型',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
current_max_id 字段和step 字段主要用于获取批量 ID,获取的批量 id 为: current_max_id ~ current_max_id+step。 version 字段主要用于解决并发问题(乐观锁), biz_type 主要用于表示业务类型。 
- 先插入一行数据(获取100个id)
INSERT INTO sequence_id_generator (id , current_max_id , step , version , biz_type ) VALUES (1, 0, 100, 0, 101); - 通过 SELECT 获取指定业务下的批量唯一 ID
SELECT current_max_id , step ,version FROM sequence_id_generator where biz_type = 101 - 再次获取100个id,更新之后重新 SELECT 即可。
update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = 101
优点
不会频繁的访问数据库,对数据库的压力小
缺点
需要将一个号段的自增ID保存到内存,增加实现难度
3. NoSql(基于Redis)
Redis实现分布式唯一ID主要是通过提供像 INCR 和 INCRBY 这样的自增原子命令,由于Redis自身的单线程的特点所以能保证生成的 ID 肯定是唯一有序的。 但是单机存在性能瓶颈,无法满足高并发的业务需求,所以可以采用集群的方式来实现。集群的方式又会涉及到和数据库集群同样的问题,所以也需要设置分段和步长来实现。
优点
Redis 实现分布式全局唯一ID,它的性能比较高,生成的数据是有序的,对排序业务有利
缺点
Redis单点故障,影响服务可用性; 用redis实现需要注意一点,要考虑到redis持久化的问题。redis有两种持久化方式RDB和AOF RDB会定时打一个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。 AOF会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的数据时间过长。
2. 算法
1. UUID
生成的UUID是由 8-4-4-4-12格式的数据组成,其中32个字符和4个连字符’ - ',一般我们使用的时候会将连字符删除 uuid.toString().replaceAll(“-”,“”)。
UUID uuid = UUID.randomUUID();
System.out.println(uuid.toString().replaceAll("-", ""));
优点
本地生成,性能高,全球唯一
缺点
没有具体的业务含义:无序的字符串,没有具体的业务含义,且不具备趋势自增特性; 存储性能差查询耗时:如果作为MySQL数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能,可以查阅 Mysql 索引原理 B+树的知识。
2. 基于雪花算法(Snowflake)模式
Snowflake生成的是Long类型的ID,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特。
Snowflake ID组成结构:正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特组成的一个Long类型。  第一个bit位(1bit):Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。 时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年 工作机器id(10bit):也被叫做workId,这个可以灵活配置,机房或者机器号组合都可以。 序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID
计算过程: 
优点
毫秒数在高位,自增序列在低位,整个ID都是趋势递增的
缺点
强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态 ID可能不是全局递增。在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况
3. 框架
1. Leaf(美团)
Leaf 提供了 号段模式 和 Snowflake(雪花算法) 这两种模式来生成分布式 ID。并且,它支持双号段,还解决了雪花 ID 系统时钟回拨问题。不过,时钟问题的解决需要弱依赖于 Zookeeper 。
Leaf 对原有的号段模式进行改进,比如它这里增加了双号段避免获取 DB 在获取号段的时候阻塞请求获取 ID 的线程。简单来说,就是我一个号段还没用完之前,我自己就主动提前去获取下一个号段
2. Tinyid(滴滴)
3. UidGenerator(百度)
雪花算法:https://www.bilibili.com/video/BV1Xa4y1i7Ax/?spm_id_from=333.337.search-card.all.click&vd_source=b901ef0e9ed712b24882863596eab0ca 来源:https://snailclimb.gitee.io/javaguide/#/docs/distributed-system/distributed-id?id=uidgenerator%e7%99%be%e5%ba%a6
|