前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分库分表之分布式id

分库分表之分布式id

原创
作者头像
Joseph_青椒
发布2023-08-04 20:14:57
3730
发布2023-08-04 20:14:57
举报
文章被收录于专栏:java_joseph

这篇专门来谈谈分布式id,也就是上一个文章抛出的问题分库分表初探-腾讯云开发者社区-腾讯云 (tencent.com)

需求

在单库下,主键id,一般通过自增id来实现,但是分库分表下。就会导致id重复的问题,

那么我们设计一个分布式id的需求,要达到哪些

1,首先是唯一,这个是必须保证的,

2、高效,分库分表下,一般面向C端是高性能的业务,性能是必要的

3、防止恶意用户根据id猜测

常见方案

数据库自增

这个方案,还是利用自增id,但是我们可以设置自增的步长来达到

比如,DB1,从1开始,每次加2,DB2,从2开始,每次加2

缺点:

这个缺点就是后续扩容的问题了,后续扩容怎么搞?是个很麻烦的问题,还有主从切换时,不一致可能导致id重复

UUID

这个的优点很明显,就是性能非常高 ,无网络消耗

缺点也很明显,没有自增特性,无序字符串,且太长了!浪费空间

Redis发号器

利用redis的INCR和INCRBY实现,原子操作,线程安全,性能不像方案1,利用数据库高,

对应的缺点是,增加了网络交互。占用资源

Snowflake雪花算法

twitter开源的分布式id算法,这个方案,不占用带宽,且有自增特性(时间戳)

缺点:依赖系统时钟

这里选择雪花算法,这个方法时很高效的,且有自增特性,还安全,因为它的自增不是按照数量的,是按照时间戳

SnowFlake算法

这里来好好讨论一下雪花算法,以及如何应用

雪花算法是用scala语言编写的,

优点是:生成id不重复,性能高,基于时间戳,有自增特性

缺点:就是因为按照时间戳,所以机器的时间种要保持一致

雪花算法的设计

  • 科普:数据类型在不同位数机器的平台下长度不同(怼面试官的严谨性)
  • 16位平台 int 2个字节16位
  • 32位平台 int 4个字节32位
  • 64位平台 int 4个字节32位
  • 雪花算法生成的数字,long类,所以就是8个byte,64bit
  • 表示的值 -9223372036854775808(-2的63次方) ~ 9223372036854775807(2的63次方-1)
  • 生成的唯一值用于数据库主键,不能是负数,所以值为0~9223372036854775807(2的63次方-1)

一秒,400w的id,肯定是够用的了,但是任何算法,都不可能做到完美,现在看一下

雪花算法的坑

1机器id,

要保证分布式id唯一,在分布式下,就要保证工作机器id不一样,否则就会出现id重复的问题

这里可能不太好理解,下面填坑的时候会讲到

2,时间回拨

分布式下,要保证各个系统的时间一致,有业务需求下,有可能就需要调整,或者开发人员操作不当

这个问题也要解决

实战部署

现在我们部署下分布式id,以及把坑给填好!

雪花算法的应用,在这里采用配置文件的形式

表的设置,在实体类种,将自增id的策略给注掉

当然这里也可把type改为雪花算法,倒是考虑到配置workId,就一并这样做了

代码语言:javascript
复制
#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

workId坑解决

看下这里,第一行和第三行,就是id生成策略采用雪花算法,但是,worid.id是取的系统的值,

这里设置一下:、

前置知识,:workId,雪花算法的定义是10位,也就是2^10=1024

这里好理解了吧 ,workId是节点,进程维度的,并不是微服务,

好,那么看如何封装组件

代码语言:javascript
复制
@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

时钟回拨问题解决

代码语言:javascript
复制
SnowflakeShardingKeyGenerator

sharding以及帮我们做了这个了,

代码语言:javascript
复制
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;
       }
   }

唯一账户id

日常开发需求种,accountNo等一些列需要配置唯一id的,又不像使用uuid,可以参考这个雪花算法,来封装一个自己的工具类

IDUtil

代码语言:javascript
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求
  • 常见方案
    • 数据库自增
      • UUID
        • Redis发号器
          • Snowflake雪花算法
          • SnowFlake算法
            • 雪花算法的设计
              • 雪花算法的坑
              • 实战部署
                • workId坑解决
                  • 时钟回拨问题解决
                  • 唯一账户id
                  相关产品与服务
                  云数据库 MySQL
                  腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档