一、UidGenerator简介

uid-generator是由百度技术部开发,项目GitHub地址 https://github.com/baidu/uid-generator
uid-generator是基于Snowflake算法实现的,与原始的snowflake算法不同在于,uid-generator支持自定义时间戳、工作机器ID和 序列号 等各部分的位数,而且uid-generator中采用用户自定义workId的生成策略。
uid-generator需要与数据库配合使用,需要新增一个WORKER_NODE表。当应用启动时会向数据库表中去插入一条数据,插入成功后返回的自增ID就是该机器的workId数据由host,port组成。
对于uid-generator ID组成结构
workId,占用了22个bit位,时间占用了28个bit位,序列化占用了13个bit位,需要注意的是,和原始的snowflake不太一样,时间的单位是秒,而不是毫秒,workId也不一样,而且同一应用每次重启就会消费一个workId。
需要的环境:JDK8+,MySQL(用于分配WorkerId)

二、雪花算法snowflake

由下图可知,雪花算法的几个核心组成部分:
1为sign标识位;
41位时间戳
10位workId(数据中心+工作机器,可以其他组成方式);
12位自增序列;
image.png
但是百度对这些组成部分稍微调整了一下:
image.png
由上图可知,UidGenerator的时间部分只有28位,这就意味着UidGenerator默认只能承受8.5年(2^28-1/86400/365)

三、如何使用UidGenerator 生成全局唯一ID

开发环境: springboot,mybatis
由于UidGenerator没有上传jar包到maven仓库上,需要从GitHub上上面下载源码,自己打成jar包安装到maven本地库中。

官方文档

UidGenerator源码地址
UidGenerator官方中文文档

1.新建springboot项目

如果不会springboot,请参考springboot教程。
pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>org.example</groupId>
  7. <artifactId>demo9-springboot</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <parent>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-parent</artifactId>
  12. <version>2.1.3.RELEASE</version>
  13. <relativePath />
  14. </parent>
  15. <dependencies>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-web</artifactId>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-test</artifactId>
  23. <scope>test</scope>
  24. </dependency>
  25. </dependencies>
  26. </project>

2.添加数据库和mybatis依赖

  1. <dependency>
  2. <groupId>org.mybatis.spring.boot</groupId>
  3. <artifactId>mybatis-spring-boot-starter</artifactId>
  4. <version>1.3.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. <version>8.0.20</version>
  10. </dependency>

3.最后添加uid-generator 依赖

  1. <dependency>
  2. <groupId>com.baidu.fsg</groupId>
  3. <artifactId>uid-generator</artifactId>
  4. <version>1.0.0-SNAPSHOT</version>
  5. </dependency>

4. 新建表结构

在数据库中创建uid-generator 依赖的表结构
sql语句如下:

  1. CREATE TABLE WORKER_NODE
  2. (
  3. ID BIGINT NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
  4. HOST_NAME VARCHAR(64) NOT NULL COMMENT 'host name',
  5. PORT VARCHAR(64) NOT NULL COMMENT 'port',
  6. TYPE INT NOT NULL COMMENT 'node type: ACTUAL or CONTAINER',
  7. LAUNCH_DATE DATE NOT NULL COMMENT 'launch date',
  8. MODIFIED datetime NOT NULL COMMENT 'modified time',
  9. CREATED datetime NOT NULL COMMENT 'created time',
  10. PRIMARY KEY(ID)
  11. )
  12. COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB;

5.复制WorkerNodeDAO

从uid-generator 源码中复制WorkerNodeDAO到自己项目,改名WorkerNodeMapper,并去掉@Repository注解,添加@Mapper

  1. @Mapper
  2. public interface WorkerNodeMapper {
  3. WorkerNodeEntity getWorkerNodeByHostPort(@Param("host") String host, @Param("port") String port);
  4. void addWorkerNode(WorkerNodeEntity workerNodeEntity);
  5. }

6.复制WORKER_NODE.xml文件

把WORKER_NODE.xml文件拷贝到resource的目录下,改名为WorkerNodeMapper.xml,并把里面的namespace=”com.baidu.fsg.uid.worker.dao.WorkerNodeDAO”改为自己项目中的WorkerNodeMapper。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.demo9.mapper.WorkerNodeMapper">
  4. <resultMap id="workerNodeRes"
  5. type="com.baidu.fsg.uid.worker.entity.WorkerNodeEntity">
  6. <id column="ID" jdbcType="BIGINT" property="id" />
  7. <result column="HOST_NAME" jdbcType="VARCHAR" property="hostName" />
  8. <result column="PORT" jdbcType="VARCHAR" property="port" />
  9. <result column="TYPE" jdbcType="INTEGER" property="type" />
  10. <result column="LAUNCH_DATE" jdbcType="DATE" property="launchDate" />
  11. <result column="MODIFIED" jdbcType="TIMESTAMP" property="modified" />
  12. <result column="CREATED" jdbcType="TIMESTAMP" property="created" />
  13. </resultMap>
  14. <insert id="addWorkerNode" useGeneratedKeys="true" keyProperty="id"
  15. parameterType="com.baidu.fsg.uid.worker.entity.WorkerNodeEntity">
  16. INSERT INTO WORKER_NODE
  17. (HOST_NAME,
  18. PORT,
  19. TYPE,
  20. LAUNCH_DATE,
  21. MODIFIED,
  22. CREATED)
  23. VALUES (
  24. #{hostName},
  25. #{port},
  26. #{type},
  27. #{launchDate},
  28. NOW(),
  29. NOW())
  30. </insert>
  31. <select id="getWorkerNodeByHostPort" resultMap="workerNodeRes">
  32. SELECT
  33. ID,
  34. HOST_NAME,
  35. PORT,
  36. TYPE,
  37. LAUNCH_DATE,
  38. MODIFIED,
  39. CREATED
  40. FROM
  41. WORKER_NODE
  42. WHERE
  43. HOST_NAME = #{host} AND PORT = #{port}
  44. </select>
  45. </mapper>

7.复制DisposableWorkerIdAssigner类

复制DisposableWorkerIdAssigner类到自己的工程中,并把里面依赖的WorkerNodeDAO 改为本地新增的WorkerNodeMapper,并添加@Component 注解

  1. @Component
  2. public class DisposableWorkerIdAssigner implements WorkerIdAssigner {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(com.baidu.fsg.uid.worker.DisposableWorkerIdAssigner.class);
  4. @Autowired
  5. private WorkerNodeMapper workerNodeDAO;
  6. @Transactional
  7. public long assignWorkerId() {
  8. // build worker node entity
  9. WorkerNodeEntity workerNodeEntity = buildWorkerNode();
  10. // add worker node for new (ignore the same IP + PORT)
  11. workerNodeDAO.addWorkerNode(workerNodeEntity);
  12. LOGGER.info("Add worker node:" + workerNodeEntity);
  13. return workerNodeEntity.getId();
  14. }
  15. private WorkerNodeEntity buildWorkerNode() {
  16. WorkerNodeEntity workerNodeEntity = new WorkerNodeEntity();
  17. if (DockerUtils.isDocker()) {
  18. workerNodeEntity.setType(WorkerNodeType.CONTAINER.value());
  19. workerNodeEntity.setHostName(DockerUtils.getDockerHost());
  20. workerNodeEntity.setPort(DockerUtils.getDockerPort());
  21. } else {
  22. workerNodeEntity.setType(WorkerNodeType.ACTUAL.value());
  23. workerNodeEntity.setHostName(NetUtils.getLocalAddress());
  24. workerNodeEntity.setPort(System.currentTimeMillis() + "-" + RandomUtils.nextInt(100000));
  25. }
  26. return workerNodeEntity;
  27. }
  28. }

8.修改mybatis配置

在application.yml配置文件中修改mybatis的配置,添加com.baidu.fsg.uid.worker.entity 扫描

  1. mybatis.mapper-locations: classpath:/mapper/*.xml
  2. mybatis.typeAliasesPackage: com.demo9.entity,com.baidu.fsg.uid.worker.entity

9.添加UidGenerator配置,并添加扫描路径

  1. @Configuration
  2. @ComponentScan(basePackages = {"com.baidu.fsg.uid"})
  3. public class CachedUidGeneratorConfig {
  4. @Bean
  5. public DefaultUidGenerator defaultUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {
  6. DefaultUidGenerator defaultUidGenerator = new DefaultUidGenerator();
  7. defaultUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
  8. defaultUidGenerator.setTimeBits(32);
  9. defaultUidGenerator.setWorkerBits(22);
  10. defaultUidGenerator.setSeqBits(9);
  11. defaultUidGenerator.setEpochStr("2020-01-01");
  12. return defaultUidGenerator;
  13. }
  14. @Bean
  15. public CachedUidGenerator cachedUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {
  16. CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
  17. cachedUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
  18. cachedUidGenerator.setTimeBits(32);
  19. cachedUidGenerator.setWorkerBits(22);
  20. cachedUidGenerator.setSeqBits(9);
  21. cachedUidGenerator.setEpochStr("2020-01-01");
  22. cachedUidGenerator.setBoostPower(3);
  23. cachedUidGenerator.setScheduleInterval(60L);
  24. return cachedUidGenerator;
  25. }
  26. }

10.添加单元测试类

只需引入cachedUidGenerator bean实例,直接调用cachedUidGenerator.getUID()方法即可生成uid

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest(classes = DemoApplication.class)
  3. public class UidGeneratorTest {
  4. @Autowired
  5. @Qualifier("cachedUidGenerator")
  6. private UidGenerator uidGenerator;
  7. @Test
  8. public void getUidTest() {
  9. Long uid = uidGenerator.getUID();
  10. System.out.println(uid);
  11. }
  12. }

每生成一个uid都会往WORKER_NODE表中插入一条记录
image.png

四、UidGenerator的说明

UidGenerator提供两种方式实现:DefaultUidGenerator和CachedUidGenerator。

DefaultUidGenerator

DefaultUidGenerator是UidGenerator 默认的实现方式

参数 说明
timeBits 相对于时间基点”2016-05-20”的增量值,单位:秒,可使用的时间为2^timeBis 秒例如:timeBits=30,则可使用230秒,约34年,timeBits=31,则可使用231秒,约68年
workerBits 机器id,最多可支持2^22约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,每次启动都会重新生成一批ID,因此重启次数也是会有限制的,后续可提供复用策略。
seqBits 每秒下的并发序列,9 bits可支持每台服务器每秒512个并发。
epochStr 指集成UidGenerator生成分布式ID服务第一次上线的时间,可配置,也一定要根据你的上线时间进行配置,因为默认的epoch时间可是2016-09-20,不配置的话,会浪费好几年的可用时间。

CachedUidGenerator

CachedUidGenerator是UidGenerator的重要改进实现

参数 说明
boostPower RingBuffer size扩容参数, 可提高UID生成的吞吐量默认:3, 原bufferSize=8192, 扩容后bufferSize= 8192 << 3 = 65536
paddingFactor 指定何时向RingBuffer中填充UID, 取值为百分比(0, 100), 默认为50举例: bufferSize=1024, paddingFactor=50 -> threshold=1024 * 50 / 100 = 512.当环上可用UID数量 < 512时, 将自动对RingBuffer进行填充补全
scheduleInterval 另外一种RingBuffer填充时机, 在Schedule线程中, 周期性检查填充默认:不配置此项, 即不实用Schedule线程. 如需使用, 请指定Schedule线程时间间隔, 单位:秒