其他分享
首页 > 其他分享> > 分布式ID生成方式

分布式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