Seata中IDworker类源码分享

举报
Kokomo 发表于 2023/12/27 09:51:41 2023/12/27
【摘要】 Seata中IDworker类源码分享

背景

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

源码解读

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

  • 位数分配
  • 获取workerID
  • 生成UId
  /**
   * 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 做了什么

  /**
   * 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的方法

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的结构,实现有更深层的了解。其实各种实现方式都是为了实现业务,每种实现方式也各有优缺点。希望各位在使用中能根据实现的场景设计出合适的结构

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。