在分库分表中必定会面临着一个问题, 就是如何快速高效的生成唯一性ID。 而网络上也有一些通用的解决方案:
优点:
缺点:
这种方式也只用依赖数据库就可以了, 不用在引入过多的依赖。
优点:
缺点:
这种方案的缺点还是很大的, 都用到分库分表了, 说明业务的增长很快, 后期很有可能会继续扩展数据库,这时候就很不方便了
Snowflake是和uuid一样的不用依赖任何第三方的id生成算法。 并且这个算法生成的id还是自动增长的数字类型的。
由于这个算法的生成依赖时间,如果不同的机器时间出现跳跃还是会可能生成一样的id的。 但是这个算法也有应对的办法,就是不同的机器使用不同的wordId。 这样两台机器无论时间怎样跳跃,生成的id都不会重复。
但是分布式应用中,各个应用很可能是随时变化的, 比如业务量激增的时候多加几个应用, 业务量减少的时候下线几个应用是常有的事情, 如何保证应用变化的时候它的wordId都不一样呢?
常用的解决方案有3中:
而百度的UidGenerator就是Snowflake结合数据库实现的id生成方案, 这个方案的实现方式是:
1. 新建一张表, 主键使用自增的’
2. 每次有应用启动的时候,在这个表中新增一条记录, 并获取返回的自增主键id
3. 使用返回的自增主键id作为Snowflake的wordId
4. 构建好了Snowflake后使用批量获取的方式获取一批id保存在内部维护的环结构中。
从实现方式发现了有几个问题。
上面只是百度的默认使用的bit位的分配比例, 但是UidGenerator支持自己定义这三个的信息的占位的大小, 这就很有自由度了, 比如你认为你的应用不需要这么多的重启次数,你可以减少WorkerBits的设置, 如果你任务你的并发量超级高,8192个id已经不能满足你的业务需要, 你可以增加SeqBits位数。 如果你任务你的应用只要有超过8.7年的生命时间, 可以增加TimeBits的位数。 具体可以根据需要来分配他们。
限制: 内部依赖了mybatis和mybatis-spring。 如果你是使用的jpa的框架, 没有使用mybatis那么你无法直接使用下面方式, 建议你下载下来源码后改造下使用, 源码地址:
https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md
<dependency>
<groupId>com.xfvape.uid</groupId>
<artifactId>uid-generator</artifactId>
<version>0.0.4-RELEASE</version>
<exclusions>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
<exclusion>
<artifactId>mybatis-spring</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
它的内部依赖有mybatis和mybatis-spring , 一般这两个依赖项目中都会有自己导入的, 防止依赖冲突需要排除掉。
可以运行下面的建表语句:
DROP TABLE IF EXISTS WORKER_NODE;
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;
需要在配置文件中增加配置:
mybatis.mapper-locations= classpath:mapper/*.xml
同时还要在mapper目录下增加如下内容的xml文件:
<?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.xfvape.uid.worker.dao.WorkerNodeDAO">
<resultMap id="workerNodeRes"
type="com.xfvape.uid.worker.entity.WorkerNodeEntity">
<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.xfvape.uid.worker.entity.WorkerNodeEntity">
INSERT INTO WORKER_NODE
(HOST_NAME,
PORT,
TYPE,
LAUNCH_DATE,
MODIFIED,
CREATED)
VALUES (
#{hostName},
#{port},
#{type},
#{launchDate},
NOW(),
NOW())
</insert>
<select id="getWorkerNodeByHostPort" resultMap="workerNodeRes">
SELECT
ID,
HOST_NAME,
PORT,
TYPE,
LAUNCH_DATE,
MODIFIED,
CREATED
FROM
WORKER_NODE
WHERE
HOST_NAME = #{host} AND PORT = #{port}
</select>
</mapper>
在启动类Bootstrap中加上如下注解:
@MapperScan(basePackages = {"com.xfvape.uid.worker.dao"})
配置如下的两个bean到容器中:
@Bean
public CachedUidGenerator cachedUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {
CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
cachedUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
cachedUidGenerator.setTimeBits(29);
cachedUidGenerator.setWorkerBits(21);
cachedUidGenerator.setSeqBits(13);
//从2020-08-06起, 可以使用8.7年
cachedUidGenerator.setEpochStr("2020-08-06");
//扩容比如设置成2, 当RingBuffer不够用默认是扩大三倍,
cachedUidGenerator.setBoostPower(2);
//当环上的缓存的uid少于25的时候, 进行新生成填充
cachedUidGenerator.setPaddingFactor(25);
//开启一个线程, 定时检查填充uid。 不采用这个方式。
//cachedUidGenerator.setScheduleInterval(60);
//两个拒绝策略使用默认的足够
//拒绝策略: 当环已满, 无法继续填充时, 默认无需指定, 将丢弃Put操作, 仅日志记录.
//cachedUidGenerator.setRejectedPutBufferHandler();
//拒绝策略: 当环已空, 无法继续获取时,默认无需指定, 将记录日志, 并抛出UidGenerateException异常.
//cachedUidGenerator.setRejectedTakeBufferHandler();
return cachedUidGenerator;
}
@Bean
public DisposableWorkerIdAssigner disposableWorkerIdAssigner() {
DisposableWorkerIdAssigner disposableWorkerIdAssigner = new DisposableWorkerIdAssigner();
return disposableWorkerIdAssigner;
}
完成上面的操作后, 配置就完成了,剩下的就是使用的问题。
使用只要注入CachedUidGenerator对象, 使用它的getUID方就可以直接获取id了。
@Resource
CachedUidGenerator cachedUidGenerator;
@Override
public Long nextId() {
return cachedUidGenerator.getUID();
}
UidGenerator对Snowflake算法进行了很多的优化, 并且灵活性很大,你完全可以根据自己的需要另外的选择算法的bit分配, 并且还可以允许你自定义拒绝策略. 使用也很简单。 还是很推荐使用的。