Slot 机制,大白话,就是分片机制。可以把时间或空间分成一个个槽,通过一定算法使用这些槽的机制。
作用是可以把数据平均分存放到某个槽空间内,比如Hash环中使用的Hash Slot。再比如数组,可以再解为是一种槽机制。 这是空间是的槽机制, 在时间维度,可以把时间分片,每隔一段时间,就是一个时间槽位。 比如:一分钟有60秒,每2秒划分一个槽位就有30个槽,那就可以执行30次; 比如:一天有86400秒,每3秒划分一个槽位就有28800个槽。
这里实现一个简单的时间槽机制,分布式场景下,通过这个机制在,去中心化的场景下,让不同的机制按照一定时间槽机制进行运作。
要求 必须保证是精准的3秒间隔,中间代码处理业务逻辑的时间必须也要计算在内。 比如,执行业务逻辑使用100毫秒,那么到下一个3秒的间隔就是2900毫秒; 如果,执行业务逻辑使用500毫秒,那么到下一个3秒的间隔就是2500毫秒; 如果,执行业务逻辑使用2900毫秒,那么到下一个3秒的间隔就是100毫秒; 保证完整的3秒,不多不少。
思路 这样的话,就要记录计算所有时间:
每一轮开始,就会有一个开始时间为起点,执行的时间就是使用时间,将这个时间录下来,并使用开始时间减去使用时间,就得到了剩余时间。 还需要一个标杆来确认每轮时间间隔。
提取需要几个参数:
INTERVAL 即是时间间隔,在逻辑上也是一个Slot。我们要做的其实就是针对这个进行操作,计算这个时间槽内的时间流逝。
@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("-----------------");
}
}
结果:
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毫秒。 这里还有问题,如果超时了怎么办?分布式环境下,如何保证多个节点之间的时间是同步的?
把上面封装成一个可以操作的方法,用来在获得和判断下一个slot的位置。产块前,需要先判断是否进入了下一个slot周期。当前时间必须大于 LatestBlockHeaderTimestamp,等于也不行说明还在当前slot周期内。
/**
* @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场景
TODO