目录
一、 Sharding-JDBC主键
二、Twitter的分布式自增ID算法Snowflake
雪花算法概述
组成结构
优点
缺点:
三、Sharding JDBC 使用Snowflake生成唯一主键ID
配置文件制定
使用DefaultKeyGenerator 类获取
对Snowflake时钟回拨问题处理
生成主键实现理解
软件项目开发中,主键自动生成是基本需求。而各个数据库对于该需求也提供了相应的支持,比如:数据库自增( MySQL, Oracle等)。但在分布式环境中,分库分表之后,不同表生成全局唯一的Id是非常棘手的问题。因为同一个逻辑表内的不同实际表之间的自增键是无法互相感知的, 这样会造成重复Id的生成。我们当然可以通过约束表生成键的规则(设置不同的起始和步长)来达到数据的不重复,但是这需要引入额外的运维力量来解决重复性问题,如果数据库节点变更会使框架缺乏扩展性。
目前有许多第三方解决方案可以完美解决这个问题,如:
而 ShardingSphere不仅提供了内置的分布式主键生成器,例如UUID、 Snowflake。还抽离出分布式主键生成器的接口(io.shardingsphere.core.keygen.KeyGenerator),方便用户自行实现自定义的自增主键生成器。
有这么一种说法,自然界中并不存在两片完全一样的雪花的。每一片雪花都拥有自己漂亮独特的形状、独一无二。雪花算法也表示生成的ID如雪花般独一无二
snowflake算法是一款本地生成的(ID生成过程不依赖任何中间件,无网络通信),保证ID全局唯一,并且ID总体有序递增,
大致由:首位无效符、时间戳差值,机器(进程)编码,序列号四部分组成,雪花算法生成的ID是纯数字且具有时间顺序的。
ShardingSphere 在分片规则配置模块可配置每个表的主键生成策略,默认使用为雪花算法(io.shardingsphere.core.keygen.DefaultKeyGenerator)
# 主键生成 sharding jdbc 默认主键算法是 64位雪花算法 sharding.jdbc.config.sharding.tables.t_order.key-generator-class-name=io.shardingsphere.core.keygen.DefaultKeyGenerator sharding.jdbc.config.sharding.tables.t_order.key-generator-column-name=id
DefaultKeyGenerator generator = new DefaultKeyGenerator();
generator.generateKey();
private boolean waitTolerateTimeDifferenceIfNeed(long currentMilliseconds) throws Throwable { try { //lastMilliseconds 最后一次生成序列时间 if (this.lastMilliseconds <= currentMilliseconds) { //如果lastMilliseconds return false; } else { long timeDifferenceMilliseconds = this.lastMilliseconds - currentMilliseconds; Preconditions.checkState(timeDifferenceMilliseconds < (long) maxTolerateTimeDifferenceMilliseconds, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", new Object[]{this.lastMilliseconds, currentMilliseconds}); Thread.sleep(timeDifferenceMilliseconds); return true; } } catch (Throwable var5) { throw var5; } }
服务器时钟回拨会导致产生重复序列,因此默认分布式主键生成器提供了一个最大容忍的时钟回拨毫秒数。如果时钟回拨的时间超过最大容忍的毫秒数值,则程序报错;如果在可容忍的范围内,默认分布式主键生成器会等待(Thread.sleep)时钟同步到最后一次主键生成的时间后再继续工作。最大咨忍的时钟回拨室秒数的默认值为0,可通过调用静态方法 Defaultkey Generator setMaxTolerate Time DifferenceMilliseconds设置
public synchronized Number generateKey() { long currentMilliseconds = timeService.getCurrentMillis(); if (this.waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) { currentMilliseconds = timeService.getCurrentMillis(); } if (this.lastMilliseconds == currentMilliseconds) { if (0L == (this.sequence = this.sequence + 1L & 4095L)) { currentMilliseconds = this.waitUntilNextTime(currentMilliseconds); } } else { this.vibrateSequenceOffset(); this.sequence = (long)this.sequenceOffset; } this.lastMilliseconds = currentMilliseconds; return currentMilliseconds - EPOCH << 22 | workerId << 12 | this.sequence; }
核心代码如下,几个实现的关键点:
synchronized保证线程安全;
如果出现时间回拨,判断时钟回拨的时间是否超过最大容忍的毫秒数值,如果超过抛出异常;
如果当前时间和上一次是同一秒时间,那么sequence自增。如果同一秒内sequence自增值超过2^13-1,那么就会自旋等待下一秒(getNextSecond);
如果是新的一秒,那么sequence重新从0开始;