目录
前言
实际开发分布式项目过程中我们会遇到分布式id的问题,解决方案有基于数据库、基于雪花算法等等。本文主要介绍基于雪花算法的百度开源UidGenerator项目与SpringBoot2.x的整合。
本文源码
UidGenerator是什么
UidGenerator是百度开源的一款分布式高性能的唯一ID生成器,是基于snowflake模型的一种ID生成器
UidGenerator的优势
- 解决了时钟回调的问题
- 使用ringbuffer,无锁进行id的生产与消费,速度非常快
- 适用于多线程,不会有单线程瓶颈
整合
本文通过SpringBoot2.x
和MyBatis
对百度UidGenerator
进行整合。
UidGenerator Maven引入
<dependency>
<groupId>com.xfvape.uid</groupId>
<artifactId>uid-generator</artifactId>
<version>0.0.4-RELEASE</version>
</dependency>
因为UidGenerator自带引入了mybatis,和自己项目有冲突的可以进行排除
排除冲突依赖
uid-generator中依赖了logback和mybatis。一般在项目搭建过程中,springboot中已经有了logback依赖,mybatis会作为单独的依赖引入。如果版本和uid-generator中的依赖不一致的话,就会导致冲突。
排除冲突的依赖如下:
<dependency>
<groupId>com.xfvape.uid</groupId>
<artifactId>uid-generator</artifactId>
<version>0.0.4-RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
这里给出完整的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.it</groupId>
<artifactId>uid-generator-demo</artifactId>
<version>1.0.0</version>
<name>uid-generator-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.xfvape.uid</groupId>
<artifactId>uid-generator</artifactId>
<version>0.0.4-RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
建表SQL
在 MySQL数据库中建一个名为 WORKER_NODE
的数据表,其 sql如下:
CREATE TABLE WORKER_NODE
(
ID BIGINT NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
HOST_NAME VARCHAR(64) NOT NULL COMMENT 'host name',
PORT VARCHAR(64) NOT NULL COMMENT 'port',
TYPE INT NOT NULL COMMENT 'node type: ACTUAL or CONTAINER',
LAUNCH_DATE DATE NOT NULL COMMENT 'launch date',
MODIFIED TIMESTAMP NOT NULL COMMENT 'modified time',
CREATED TIMESTAMP NOT NULL COMMENT 'created time',
PRIMARY KEY(ID)
)
COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB;
建表时可能会报错,原因是该建表语句定义了两个TIMESTAMP字段,因为mysql 低版本控制比较严格
解决办法:
方式一:直接把TIMESTAMP改成DATETIME 即可
方式二:建表前先设置sql_mode:
set sql_mode="NO_ENGINE_SUBSTITUTION";
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/ssm?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: 123456
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.it.domain
注意: 修改为自己的数据库连接信息
实体类
package com.it.domain;
import lombok.Data;
import java.util.Date;
@Data
public class WorkerNode {
private Long id;
private String hostName;
private String port;
private Integer type;
private Date launchDate;
private Date modified;
private Date created;
}
mapper
package com.it.mapper;
import com.it.domain.WorkerNode;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface WorkerNodeMapper {
int addWorkerNode(WorkerNode workerNodeEntity);
WorkerNode getWorkerNodeByHostPort(@Param("host") String host, @Param("port") String port);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.mapper.WorkerNodeMapper">
<resultMap id="BaseResultMap"
type="com.it.domain.WorkerNode">
<id column="ID" jdbcType="BIGINT" property="id" />
<result column="HOST_NAME" jdbcType="VARCHAR" property="hostName" />
<result column="PORT" jdbcType="VARCHAR" property="port" />
<result column="TYPE" jdbcType="INTEGER" property="type" />
<result column="LAUNCH_DATE" jdbcType="DATE" property="launchDate" />
<result column="MODIFIED" jdbcType="TIMESTAMP" property="modified" />
<result column="CREATED" jdbcType="TIMESTAMP" property="created" />
</resultMap>
<insert id="addWorkerNode" useGeneratedKeys="true" keyProperty="id"
parameterType="com.it.domain.WorkerNode">
INSERT INTO WORKER_NODE
(HOST_NAME,
PORT,
TYPE,
LAUNCH_DATE,
MODIFIED,
CREATED)
VALUES (
#{hostName},
#{port},
#{type},
#{launchDate},
NOW(),
NOW())
</insert>
<select id="getWorkerNodeByHostPort" resultMap="BaseResultMap ">
SELECT
ID,
HOST_NAME,
PORT,
TYPE,
LAUNCH_DATE,
MODIFIED,
CREATED
FROM
WORKER_NODE
WHERE
HOST_NAME = #{host} AND PORT = #{port}
</select>
</mapper>
注意修改resultMap中的type和insert中的keyProperty为自身项目的实体类地址
service
package com.it.service;
public interface IWorkerNodeService {
public long genUid();
}
package com.it.service.impl;
import com.it.service.IWorkerNodeService;
import com.xfvape.uid.UidGenerator;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class WorkerNodeServiceImpl implements IWorkerNodeService {
@Resource
private UidGenerator uidGenerator;
@Override
public long genUid() {
return uidGenerator.getUID();
}
}
controller
package com.it.controller;
import com.it.service.IWorkerNodeService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class WorkerNodeServiceController {
@Resource
private IWorkerNodeService workerNodeService;
/**
* 集成百度uid-generator生成id
* @return
*/
@GetMapping("/baidu/uid")
public long baiduUid(){
return workerNodeService.genUid();
}
}
重写WorkerIdAssigner接口
package com.it.config;
import com.it.domain.WorkerNode;
import com.it.mapper.WorkerNodeMapper;
import com.xfvape.uid.utils.DockerUtils;
import com.xfvape.uid.utils.NetUtils;
import com.xfvape.uid.worker.WorkerIdAssigner;
import com.xfvape.uid.worker.WorkerNodeType;
import org.apache.commons.lang.math.RandomUtils;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
public class DisposableWorkerIdAssigner implements WorkerIdAssigner {
@Resource
private WorkerNodeMapper workerNodeMapper;
@Override
@Transactional
public long assignWorkerId() {
WorkerNode workerNode = buildWorkerNode();
workerNodeMapper.addWorkerNode(workerNode);
return workerNode.getId();
}
private WorkerNode buildWorkerNode() {
WorkerNode workNode = new WorkerNode();
if (DockerUtils.isDocker()) {
workNode.setType(WorkerNodeType.CONTAINER.value());
workNode.setHostName(DockerUtils.getDockerHost());
workNode.setPort(DockerUtils.getDockerPort());
workNode.setLaunchDate(new Date());
} else {
workNode.setType(WorkerNodeType.ACTUAL.value());
workNode.setHostName(NetUtils.getLocalAddress());
workNode.setPort(System.currentTimeMillis() + "-" + RandomUtils.nextInt(100000));
workNode.setLaunchDate(new Date());
}
return workNode;
}
}
将uid-generator核心对象装配为spring的bean
uid-generator提供了两种生成器: DefaultUidGenerator、CachedUidGenerator。如对UID生成性能有要求, 请使用CachedUidGenerator。
package com.it.config;
import com.xfvape.uid.UidGenerator;
import com.xfvape.uid.impl.CachedUidGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WorkerNodeConfig {
@Bean("disposableWorkerIdAssigner")
public DisposableWorkerIdAssigner disposableWorkerIdAssigner(){
return new DisposableWorkerIdAssigner();
}
@Bean("cachedUidGenerator")
public UidGenerator uidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner){
CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
cachedUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
return cachedUidGenerator;
}
}
测试
启动项目,访问 http://localhost:8080/baidu/uid