1、UUID
实现方式
String uuid = UUID.randomUUID().toString().replaceAll("-","");
优点:
- 生成简单,本地生成无网络消耗,具有唯一性
缺点:
- 无序的字符串,不具备趋势自增特性
- 没有具体的业务含义
- 长度过长,存储以及查询对MySQL的性能消耗较大。
2、数据库自增id
实现方式
需要一个单独的MySQL表用来生成ID
CREATE DATABASE `SEQ_ID`;
CREATE TABLE SEQID.SEQUENCE_ID (
id bigint(20) unsigned NOT NULL auto_increment,
stub char(10) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY value(stub)
) ENGINE=MyISAM;
begin:
replace into SEQUENCE_ID(stub) VALUES('anyword');
select last_insert_id();
commit;
优点:
- 实现简单,ID单调自增,数值类型查询速度快
缺点:
- DB单点存在宕机风险,无法扛住高并发场景
那如果采用基于数据库的主从或者多主模式,就能够解决上面的高并发场景了吗,答案是否定的
问题
- 主从复制间存在延时,主机刚刚插入一个新的id,还没有同步到从机就挂机了。从机变为主机后会生成重复id
- 多主模式下要设置起始值和自增步长,且不利于后续扩容,而且实际上单个数据库自身压力还是大,依旧无法满足高并发场景。
3、基于数据库的号段模式
号段模式是当下分布式ID生成器的主流实现方式之一,号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。表结构如下:
CREATE TABLE id_generator (
id int(10) NOT NULL,
max_id bigint(20) NOT NULL COMMENT '当前最大id',
step int(20) NOT NULL COMMENT '号段的步长',
biz_type int(20) NOT NULL COMMENT '业务类型',
version int(20) NOT NULL COMMENT '版本号',
PRIMARY KEY (`id`)
)
- biz_type :代表不同业务类型
- max_id :当前最大的可用id
- step :代表号段的长度
- version :是一个乐观锁,每次都更新version,保证并发时数据的正确性
等这批号段ID用完,再次向数据库申请新号段,对max_id字段做一次update操作,update max_id= max_id + step
,update成功则说明新号段获取成功,新的号段范围是(max_id ,max_id +step]。
update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = XXX
由于多业务端可能同时操作,所以采用版本号version乐观锁方式更新,这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多
4、基于Redis模式
Redis也同样可以实现,原理就是利用redis的 incr命令实现ID的原子性自增。
优点:
- 不依赖于数据库,灵活方便,且性能优于数据库。
- 数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
- 如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
- 防止redis挂机需要配置集群,RDB方式会有延时,而AOF恢复的时间较长(要执行较多的incr命令)
5、雪花算法(Snowflake)模式
Snowflake生成的是Long类型的ID,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特。
- 第一个bit位(1bit):代表正负,正数是0,负数是1,默认为0。
- 时间戳部分(41bit):毫秒级的时间,(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位大概使用69年
- 工作机器id(10bit):机房或者机器号组合都可以。
- 序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID
优点:
- 不依赖于数据库,灵活方便,且性能优于数据库。
- ID按照时间在单机上是递增的。
缺点:
- 在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。
6、百度的uid-generator
uid-generator是基于Snowflake算法实现的,与原始的snowflake算法不同在于,uid-generator支持自定义时间戳、工作机器ID和 序列号 等各部分的位数,而且uid-generator中采用用户自定义workId的生成策略。
- workId,占用了22个bit位。需要与数据库配合使用,需要新增一个WORKER_NODE表。当应用重启时会向数据库表中去插入一条数据,插入成功后返回的自增ID就是该机器的workId,应用运行时workId不变
- 时间占用了28个bit位,时间的单位改成了秒
- 序列化占用了13个bit位
解决时钟回拨问题
- synchronized保证线程安全;
- 如果时间有任何的回拨,那么直接抛出异常;
- 如果当前时间和上一次是同一秒时间,那么sequence自增。如果同一秒内自增值超过2^13-1,那么就会自旋等待下一秒(getNextSecond);
- 如果是新的一秒,那么sequence重新从0开始;
7、美团(Leaf)
美团的Leaf也是一个分布式ID生成框架。它非常全面,即支持号段模式,也支持snowflake模式。号段模式这里就不介绍了,和上面的分析类似。
Leaf中的snowflake模式和原始snowflake算法的不同点,也主要在workId的生成,Leaf中workId是基于ZooKeeper的顺序Id来生成的,每个应用在使用Leaf-snowflake时,在启动时都会都在Zookeeper中生成一个顺序Id,相当于一台机器对应一个顺序节点,也就是一个workId。