这篇专门来谈谈分布式id,也就是上一个文章抛出的问题分库分表初探-腾讯云开发者社区-腾讯云 (tencent.com)
在单库下,主键id,一般通过自增id来实现,但是分库分表下。就会导致id重复的问题,
那么我们设计一个分布式id的需求,要达到哪些
1,首先是唯一,这个是必须保证的,
2、高效,分库分表下,一般面向C端是高性能的业务,性能是必要的
3、防止恶意用户根据id猜测
这个方案,还是利用自增id,但是我们可以设置自增的步长来达到
比如,DB1,从1开始,每次加2,DB2,从2开始,每次加2
缺点:
这个缺点就是后续扩容的问题了,后续扩容怎么搞?是个很麻烦的问题,还有主从切换时,不一致可能导致id重复
这个的优点很明显,就是性能非常高 ,无网络消耗
缺点也很明显,没有自增特性,无序字符串,且太长了!浪费空间
利用redis的INCR和INCRBY实现,原子操作,线程安全,性能不像方案1,利用数据库高,
对应的缺点是,增加了网络交互。占用资源
twitter开源的分布式id算法,这个方案,不占用带宽,且有自增特性(时间戳)
缺点:依赖系统时钟
这里选择雪花算法,这个方法时很高效的,且有自增特性,还安全,因为它的自增不是按照数量的,是按照时间戳
这里来好好讨论一下雪花算法,以及如何应用
雪花算法是用scala语言编写的,
优点是:生成id不重复,性能高,基于时间戳,有自增特性
缺点:就是因为按照时间戳,所以机器的时间种要保持一致
一秒,400w的id,肯定是够用的了,但是任何算法,都不可能做到完美,现在看一下
1机器id,
要保证分布式id唯一,在分布式下,就要保证工作机器id不一样,否则就会出现id重复的问题
这里可能不太好理解,下面填坑的时候会讲到
2,时间回拨
分布式下,要保证各个系统的时间一致,有业务需求下,有可能就需要调整,或者开发人员操作不当
这个问题也要解决
现在我们部署下分布式id,以及把坑给填好!
雪花算法的应用,在这里采用配置文件的形式
表的设置,在实体类种,将自增id的策略给注掉
当然这里也可把type改为雪花算法,倒是考虑到配置workId,就一并这样做了
#id生成策略
spring.shardingsphere.sharding.tables.traffic.key-generator.column=id
spring.shardingsphere.sharding.tables.traffic.key-generator.props.worker.id=${workId}
spring.shardingsphere.sharding.tables.traffic.key-generator.type=SNOWFLAKE
看下这里,第一行和第三行,就是id生成策略采用雪花算法,但是,worid.id是取的系统的值,
这里设置一下:、
前置知识,:workId,雪花算法的定义是10位,也就是2^10=1024
这里好理解了吧 ,workId是节点,进程维度的,并不是微服务,
好,那么看如何封装组件
@Configuration
@Slf4j
public class SnowFlakeWordIdConfig {
/**
* 动态指定sharding jdbc 的雪花算法中的属性work.id属性
* 通过调用System.setProperty()的方式实现,可用容器的 id 或者机器标识位
* workId最大值 1L << 100,就是1024,即 0<= workId < 1024
* {@link SnowflakeShardingKeyGenerator#getWorkerId()}
*
*/
static {
try {
InetAddress ip4 = Inet4Address.getLocalHost();
String addressIp = ip4.getHostAddress();
String workId = (Math.abs(addressIp.hashCode())%1024)+"";
System.setProperty("workId", workId);
log.info("workId:{}",workId);
} catch (UnknownHostException e) {
throw new BizException(BizCodeEnum.OPS_NETWORK_ADDRESS_ERROR);
}
}
}
这里只点一下为何取模1024,就是刚才所说的,workid嘛,2^10=1024,然后为何workId要不同,在哪个维度不同,上面的也讲了!
好,坑一解决完了
看坑2
SnowflakeShardingKeyGenerator
sharding以及帮我们做了这个了,
public synchronized Comparable<?> 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 | this.getWorkerId() << 12 | this.sequence;
}
private boolean waitTolerateTimeDifferenceIfNeed(long currentMilliseconds) {
try {
//如果当前时间大于等于上一次id生成时间,说明不需要处理回拨问题
if (this.lastMilliseconds <= currentMilliseconds) {
return false;
} else {
//这里是处理时间回拨问题的逻辑,但是要注意是否在容忍范围内,
//不在容错范围则等待,睡眠,直至到最大容忍范围。
long timeDifferenceMilliseconds = this.lastMilliseconds - currentMilliseconds;
Preconditions.checkState(timeDifferenceMilliseconds < (long)this.getMaxTolerateTimeDifferenceMilliseconds(), "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;
}
}
日常开发需求种,accountNo等一些列需要配置唯一id的,又不像使用uuid,可以参考这个雪花算法,来封装一个自己的工具类
IDUtil
public class IDUtil {
private static SnowflakeShardingKeyGenerator shardingKeyGenerator = new SnowflakeShardingKeyGenerator();
/**
* 雪花算法生成器,配置workId,避免重复
*
* 10进制 654334919987691526
* 64位 0000100100010100101010100010010010010110000000000000000000000110
*
* {@link SnowFlakeWordIdConfig}
*
* @return
*/
public static Comparable<?> geneSnowFlakeID(){
return shardingKeyGenerator.generateKey();
}
}
当然这个也需要我们配置不同的workId来避免重复问题。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。