前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Seata中IDworker类源码分享

Seata中IDworker类源码分享

原创
作者头像
Kokomo
发布2023-11-14 15:14:08
2010
发布2023-11-14 15:14:08
举报
文章被收录于专栏:灵墨AI探索室灵墨AI探索室

背景

在之前的文章中,我们介绍了几种Uid的生成策略,其中涉及到了Seata的IdWorker类,但由于篇幅原因,并未展开讲,Seata的这个实现具有很强的代表性,在本文中,笔者就带大家详细的了解一下这个实现。

源码解读

这个类大体上可分为三个部分

  • 位数分配
  • 获取workerID
  • 生成UId
代码语言:java
复制
  /**
   * Start time cut (2020-05-03)
   */
  private final long twepoch = 1588435200000L;
  
  /**
   * The number of bits occupied by sequence
   */
  private final int sequenceBits = 12;
  
  /**
   * timestamp and sequence mix in one Long
   * highest 11 bit: not used
   * middle  41 bit: timestamp
   * lowest  12 bit: sequence
   */
  private AtomicLong timestampAndSequence;
  
  public IdWorker(Long workerId) {
      initTimestampAndSequence();
      initWorkerId(workerId);
  }
  
  /**
   * init first timestamp and sequence immediately
   */
  private void initTimestampAndSequence() {
      long timestamp = getNewestTimestamp();
      long timestampWithSequence = timestamp << sequenceBits;
      this.timestampAndSequence = new AtomicLong(timestampWithSequence);
  }

private long getNewestTimestamp() {
      return System.currentTimeMillis() - twepoch;
  }

这个构造器主要用来初始化时间戳及占位 twepoch 是初始时间 getNewestTimestamp()减去初始时间可以延长整个Uid的使用寿命,不了解的小伙伴可以翻翻之前的文章。initTimestampAndSequence 主要是将时间位左移12位,大家可以看到timestampAndSequence的定义 高11位是标志位和workerid位 ,中间41位是时间戳,低12位是序列

下面我们再看下initWorkerId 做了什么

代码语言:java
复制
  /**
   * Maximum supported machine id, the result is 1023
   */
  private final int maxWorkerId = ~(-1 << workerIdBits);
  /**
   * The number of bits occupied by timestamp
   */
  private final int timestampBits = 41;
  /**
   * The number of bits occupied by sequence
   */
  private final int sequenceBits = 12;
  
 private void initWorkerId(Long workerId) {
      if (workerId == null) {
          workerId = generateWorkerId();
      }
      if (workerId > maxWorkerId || workerId < 0) {
          String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId);
          throw new IllegalArgumentException(message);
      }
      this.workerId = workerId << (timestampBits + sequenceBits);
  }
  
  private long generateWorkerId() {
      try {
          return generateWorkerIdBaseOnMac();
      } catch (Exception e) {
          return generateRandomWorkerId();
      }
  }
  
  private long generateWorkerIdBaseOnMac() throws Exception {
      Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();
      while (all.hasMoreElements()) {
          NetworkInterface networkInterface = all.nextElement();
          boolean isLoopback = networkInterface.isLoopback();
          boolean isVirtual = networkInterface.isVirtual();
          if (isLoopback || isVirtual) {
              continue;
          }
          byte[] mac = networkInterface.getHardwareAddress();
          return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
      }
      throw new RuntimeException("no available mac found");
  }
  
  private long generateRandomWorkerId() {
      return new Random().nextInt(maxWorkerId + 1);
  }

生成workerid时 如果未指定 则先采用网卡地址生成,注意在生成时左移8位 主要是避免k8环境下前面相同的情况,如果网卡生成时错误则随机生成,在生成后将其左移,使其放在高位,此时,UId的整体结构已经确定。下面我们看一下具体生成Uid的方法

代码语言:java
复制
public long nextId() {
      waitIfNecessary();
      long next = timestampAndSequence.incrementAndGet();
      long timestampWithSequence = next & timestampAndSequenceMask;
      return workerId | timestampWithSequence;
  }
  
  private void waitIfNecessary() {
      long currentWithSequence = timestampAndSequence.get();
      long current = currentWithSequence >>> sequenceBits;
      long newest = getNewestTimestamp();
      if (current >= newest) {
          try {
              Thread.sleep(5);
          } catch (InterruptedException ignore) {
              // don't care
          }
      }
  }

nextId() 方法返回的就是UId ,每次获取id时都会判断当前持有的时间戳与系统时间戳,如果持有的时间戳大于等于系统时间 则睡眠5ms,在时间校验通过后就对时间戳及序列加一。这也是为什么Seata会调整原雪花id结构的原因,这样编程起来十分方便。之前的文章我们提到过,针对时钟回拨的情况,大部分采用的都是启动时获取一次时间,后续采用累加的方式,但这种方式有个缺点,就是时间是不能无限超前使用的,如果超前使用了很久,那么在下次重启后获取的时间戳是一定重复的(前提是在一个workerid内),所以,大家能更好的理解为什么百度Uid的workerid是每次都生成新的了吧,另外百度Uid的workerid的位数也做了相应的调整,而Seata的workerid是固定的,如果不限制超前就很容易出现之前所说的问题,此外,也可以采用延迟启动的方式,在获取时间戳后,延迟一段时间,用来应对超前的消费。

尾声

本篇文章是对之前UId文章的补充,通过这次源码的分享,希望大家能触类旁通,举一反三,能够对各种UId的结构,实现有更深层的了解。其实各种实现方式都是为了实现业务,每种实现方式也各有优缺点。希望各位在使用中能根据实现的场景设计出合适的结构

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

“邀请人:“努力的小雨”

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 源码解读
  • 尾声
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档