分布式ID生成方式
作者:互联网
1.前言
分布式id的来源主要是针对分布式的整体应用,在每秒可成品千万级别的数据。对于这样的大批量数据,必定需要分库分表,那么针对常用的自增主键(MySQL)或序列增长(Oracle),同一业务的分表,会存在相同的主键,对于数据处理来说,会显得异常麻烦。那就必须采用更有效的方式来解决这种问题,实现ID的全局唯一。
2.主键策略分类
2.1雪花算法(SnowFlake)
这种主键生成方式是目前各大中小型系统用的最多的。具体用法见雪花算法生成主键。生成的结果是19位long类型,有个缺点就是它目前只能使用70年。
2.2UUID算法
对于UUID应该都不陌生,生成方式如下:
String uuid = UUID.randomUUID().toString().replace("-", "");
其生成的是一个全局唯一的32位的字符串(通常会把其中的"-"替换掉)。例如 a902dd6953a04d7daeebff4f4ff70648 。
也就因为其长度达32位,生成的值也没有一定的规则,对于数据库来说,不仅会占用一定的空间,而且索引是随机分布的,会导致查询性能降低。故目前一般不使用这种方式,但UUID可用在请求作为每次请求的ID.
2.3基于Redis的原子性自增方式
Redis是单线程的,其内部命令都是原子性执行的,其有一种方式是可以快速实现元素的自增而又是全局唯一的,那就是incr命令。
incr seq_id
默认从0开始,每执行一次,元素都会增加1,同时会返回增加后的值。即使是redis集群,也会支持数据的共享。
但对于每秒千万级别的数据,每次都从reids中获取主键,对redis的压力非常大,很可能造成服务经常宕机。其实可以采用分段模式,也就是说,每次不从redis取一个,而是取多个(例如100个),其命令如下:
incrby user_id 100
默认从0开始,每执行一次,元素都会增加100(以最后指定的步长为准),同时会返回增加后的值。那么在应用中将返回的值加载到内存中使用,使用完后再去获取。那么应用在这里获取到的是最大值,如何指定最小值呢?也就是说从哪个值开始使用呢,实际和步长有关,最小值=当前值-步长。
2.4号段模式
号段模式和redis模式思想类似,在数据库专门设计一个表来存储各个业务主键的号段信息,每次从数据库批量获取自增ID(也就是号段范围)加载到内存中使用,这段id使用完后再去数据库获取。其表如下:
CREATE TABLE s_id_generator ( id int(10) NOT NULL auto_increment, 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`) )
其中字段version 是一个乐观锁,每次都更新version,保证并发时数据的正确性。例如业务是1001的号段信息如下:
每次获取是都先更新号段信息,然后再查询:
update s_id_generator set max_id = max_id + step, version = version + 1 where version = # {version} and biz_type = # {bizType }; select max_id,version from s_id_generator order by version desc limit 1;
获取的号段范围是( max_id, max_id + step ]。
2.5百度(UidGenerator)
UidGenerator是百度技术团队开发的, 基于Snowflake算法的唯一ID生成器。github地址https://github.com/baidu/uid-generator。
接入SpringBoot的步骤如下:(默认已导入mybatis、mysql等依赖并配置数据库连接信息)
1)下载源码
2)使用命令行打jar包
mvn install -Dmaven.test.skip=true
指定打包时跳过测试,使用默认的版本号信息。
3)在项目中引入jar
把生产的jar包放到资源目录的lib目录中
在pom.xml引入
<dependency> <groupId>com.baidu.fsg</groupId> <artifactId>uid-generator</artifactId> <version>1.0.0-SNAPSHOT</version> <scope>system</scope> <type>jar</type> <systemPath>${project.basedir}/src/main/resources/lib/uid-generator-1.0.0-SNAPSHOT.jar</systemPath> </dependency>
4)创建表
将源码中script/WORKER_NODE.sql拿到数据库执行。
5)将需要的文件复制到项目中
将源码中dao目录下的WorkerNodeDAO复制到项目的dao目录中,修改类上的注解为@Mapper(也可命名为WorkerNodeMapper)
将源码中resources/META-INF/mybatis/mapper目录下的WORKER_NODE.xml复制到项目的resource/mapper目录中,改名WorkerNodeMapper.xml并修改namespace
将源码中DisposableWorkerIdAssigner类复制到项目的config目录中,并把里面依赖的WorkerNodeDAO改为本地新增的WorkerNodeDAO,把此类注入Spring中
6)配置生成构造器
package com.zxh.bootdemo0705.config; import com.baidu.fsg.uid.impl.CachedUidGenerator; import com.baidu.fsg.uid.impl.DefaultUidGenerator; import com.baidu.fsg.uid.worker.WorkerIdAssigner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /*** * 构造器配置类 */ @Configuration public class CachedUidGeneratorConfig { /** * 注入默认构造器 * * @param disposableWorkerIdAssigner * @return */ @Bean public DefaultUidGenerator defaultUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) { DefaultUidGenerator defaultUidGenerator = new DefaultUidGenerator(); defaultUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner); /** * 对于并发数要求不高、期望长期使用的应用, 可增加timeBits位数, 减少seqBits位数 * 如{"workerBits":23,"timeBits":31,"seqBits":9}, 可支持28个节点以整体并发量14400 UID/s的速度持续运行68年 * * 对于节点重启频率频繁、期望长期使用的应用, 可增加workerBits和timeBits位数, 减少seqBits位数 * 如{"workerBits":27,"timeBits":30,"seqBits":6}, 可支持37个节点以整体并发量2400 UID/s的速度持续运行34年 */ //timeBits,相对于时间基点"2016-05-20"的增量值,单位:秒,可使用的时间为2^timeBis 秒 defaultUidGenerator.setTimeBits(32); // 机器id,最多可支持2^22约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃 defaultUidGenerator.setWorkerBits(22); // 每秒下的并发序列,9 bits可支持每台服务器每秒512个并发。 defaultUidGenerator.setSeqBits(9); //设置时间基点 defaultUidGenerator.setEpochStr("2020-01-01"); return defaultUidGenerator; } /** * 注入缓存构造器 * * @param disposableWorkerIdAssigner * @return */ @Bean public CachedUidGenerator cachedUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) { CachedUidGenerator cachedUidGenerator = new CachedUidGenerator(); cachedUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner); cachedUidGenerator.setTimeBits(32); cachedUidGenerator.setWorkerBits(22); cachedUidGenerator.setSeqBits(9); cachedUidGenerator.setEpochStr("2020-01-01"); //RingBuffer size扩容参数, 可提高UID生成的吞吐量 cachedUidGenerator.setBoostPower(3); cachedUidGenerator.setScheduleInterval(60L); return cachedUidGenerator; } }
通过上述可配置项来配置id的生成策略。
7)测试生成id
@Autowired private UidGenerator cachedUidGenerator; @Test public void test() { Long uid = cachedUidGenerator.getUID(); System.out.println(uid); }
生成的结果例如"35967776271113728",长度是17位。没生成一次,都会记录到上述表中
2.6美团(Leaf)
由美团公司研发,github地址https://github.com/Meituan-Dianping/Leaf。
支持号段模式和snowflake模式,通过配置进行开启或关闭。若使用号段模式,则需创建表记录号段信息;若使用snowflake模式,则开启即可使用。其官方文档提供了详细的接入说明,在此不再赘述。
2.7滴滴(Tinyid)
由滴滴开发,github地址https://github.com/didi/tinyid。
采用号段模式,和Leaf的实现原理类似。个人认为其官方文档写的过于粗略,在此略。
标签:cachedUidGenerator,生成,号段,version,defaultUidGenerator,ID,com,id,分布式 来源: https://www.cnblogs.com/zys2019/p/16469198.html