Seata中IDworker类源码分享

向量数据库大模型关系型数据库

picture.image

背景

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

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
亿万用户下高可用融合直播的应用实践
直播融合 CDN 调度系统承担了公司内所有直播流量的接入工作,对高并发高带宽场景支持友好,有完善的体系进行容灾降级、质量优化、成本优化。本次演讲将带大家了解直播融合 CDN 调度系统的整体架构及在抖音上的应用。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论