时间轮 slot 机制实现

2023-10-23 14:29:32 浏览数 (2)

slot机制

Slot 机制,大白话,就是分片机制。可以把时间或空间分成一个个槽,通过一定算法使用这些槽的机制。

有什么用?

作用是可以把数据平均分存放到某个槽空间内,比如Hash环中使用的Hash Slot。再比如数组,可以再解为是一种槽机制。 这是空间是的槽机制, 在时间维度,可以把时间分片,每隔一段时间,就是一个时间槽位。 比如:一分钟有60秒,每2秒划分一个槽位就有30个槽,那就可以执行30次; 比如:一天有86400秒,每3秒划分一个槽位就有28800个槽。

这里实现一个简单的时间槽机制,分布式场景下,通过这个机制在,去中心化的场景下,让不同的机制按照一定时间槽机制进行运作。

实现

要求 必须保证是精准的3秒间隔,中间代码处理业务逻辑的时间必须也要计算在内。 比如,执行业务逻辑使用100毫秒,那么到下一个3秒的间隔就是2900毫秒; 如果,执行业务逻辑使用500毫秒,那么到下一个3秒的间隔就是2500毫秒; 如果,执行业务逻辑使用2900毫秒,那么到下一个3秒的间隔就是100毫秒; 保证完整的3秒,不多不少。

思路 这样的话,就要记录计算所有时间:

  1. 标记当前开始时间
  2. 记录业务逻辑处理的时间
  3. 计算出下一次间隔时间

每一轮开始,就会有一个开始时间为起点,执行的时间就是使用时间,将这个时间录下来,并使用开始时间减去使用时间,就得到了剩余时间。 还需要一个标杆来确认每轮时间间隔

提取需要几个参数:

  1. INTERVAL 时间间隔
  2. current 当前时间
  3. useTime 使用的时间
  4. stillTime 剩余时间

INTERVAL 即是时间间隔,在逻辑上也是一个Slot。我们要做的其实就是针对这个进行操作,计算这个时间槽内的时间流逝。

代码语言:javascript复制
@Test
public void buildInterval() throws InterruptedException {
  int interval = 3000;
  long nextTime;
  long currentSlot = 0;
  for (int i = 0; i < 1000; i  ) {
    // 获取当前时间,逻辑上是当前slot内的起始时间
    long current = System.currentTimeMillis();
    // do something
    
    // 计算出当前 slot 的剩余时间
    long stillTime = interval - current % interval;
    System.out.println("i: "   i   ", interval: "   stillTime);
    Thread.sleep(stillTime);

    // 获得下一个 slot 的时间
    nextTime = current   stillTime;
    System.out.println("nextTime: "   nextTime);
    currentSlot = currentSlot   nextTime % interval   1;
    System.out.println("currentSlot: "   currentSlot);
    System.out.println("-----------------");
  }
}

结果:

代码语言:javascript复制
i: 0, interval: 2272
nextTime: 1647172716000
currentSlot: 1
-----------------
i: 1, interval: 2999
nextTime: 1647172719000
currentSlot: 2
-----------------
i: 2, interval: 2994
nextTime: 1647172722000
currentSlot: 3
-----------------
i: 3, interval: 2997
nextTime: 1647172725000
currentSlot: 4
-----------------
i: 4, interval: 2995
nextTime: 1647172728000
currentSlot: 5
-----------------
i: 5, interval: 2999
nextTime: 1647172731000
currentSlot: 6
-----------------
i: 6, interval: 2995
nextTime: 1647172734000
currentSlot: 7
-----------------
i: 7, interval: 2999
nextTime: 1647172737000
currentSlot: 8
-----------------

现在可以完整复现出一个完美情况下的Slot机制,可以看到每个时间戳之间的差距刚好是3000毫秒。 这里还有问题,如果超时了怎么办?分布式环境下,如何保证多个节点之间的时间是同步的?

getSlot 方法

把上面封装成一个可以操作的方法,用来在获得和判断下一个slot的位置。产块前,需要先判断是否进入了下一个slot周期。当前时间必须大于 LatestBlockHeaderTimestamp,等于也不行说明还在当前slot周期内。

代码语言:javascript复制
/**
 * @author liukai
 */
public class SlotTest {

  private static final long BLOCK_PRODUCED_INTERVAL = 3000;

  // 算出下一个 slot 的时间点

  /**
   * 算出下一个 slot 时间点
   *     slot机制调定每3秒为了个slot,一天有86000 秒,则一天有 86400 / 3 = 28800(slot)
   *     00: 00
   *     00: 03
   *     00: 06
   *     00: 09
   *     00: 12
   *     ...
   *     以此类推
   *     该方法算出上一个区块的时间,到下一个slot的精确slot是在什么位置
   * @param slot
   * @return
   */
  public static long getTime(long slot) {
    long interval = BLOCK_PRODUCED_INTERVAL;
    // 最后一块高度的时间戳
    long time = getLatestBlockHeaderTimestamp();
    // 算出一个不带余数,整数如: 1600000123 计处后得到余数:123;
    // 1600000123 - 123 = 1600000000
    time = time - ((time - getGenesisBlockTime()) % interval);
    return time   interval * slot;
  }

  public static long getSlot(long time) {
    long firstSlotTime = getTime(1);
    // 只有当前时间大于 产块时间,说明已经进入下一个slot,等于都不行
    if (time < firstSlotTime) {
      return 0;
    }
    return (time - firstSlotTime) / BLOCK_PRODUCED_INTERVAL   1;
  }

  private static long getGenesisBlockTime() {
    return 0;
  }

  /**
   * 获得区块头时间戳
   *  实际场景中,这个是间一定比当前时间戳 System.currentTimeMillis() 早大概0-3000毫秒
   * @return
   */
  // 在线时间戳转换工具 https://tool.lu/timestamp/
  private static long getLatestBlockHeaderTimestamp() {
    return System.currentTimeMillis();
//    return 1652849462102L; //2022-04-26 14:57:09
//    return 1650956726000L; //2022-04-26 15:05:26
  }

  public static void main(String[] args) {
//    测试数据1:时间和 LatestBlockHeaderTimestamp 相等
//    long current = System.currentTimeMillis();

//    测试数据2:current > LatestBlockHeaderTimestamp
//    long current = System.currentTimeMillis()   1000;

//    测试数据3:
    long current = System.currentTimeMillis()   3000;
    long time3000 = getTime(1);
    long time6000 = getTime(2);

    System.out.println("当前时间:"   current);
    System.out.println("下一个slot 时间点:"   time3000);
    System.out.println("下一个时间间格6000:"   time6000);
    long slot = getSlot(current   50);
    System.out.println(slot);
    if (slot == 0) {
      System.out.println("NOT_TIME_YET");
    } else {
      System.out.println("PRODUCT slot: "   slot);
    }
  }
}

结果:

当前时间:1652857261155 下一个时间间格3000:1652857260000 下一个时间间格6000:1652857263000 1 PRODUCT slot: 1

模拟多节点Slot

不同机器,不同网络环境,非中心化节点之间Slot场景

TODO

0 人点赞