确定到期 - 分布式节点 - 无需同步时钟

Determining expiry - distributed nodes - without syncing the clocks

我有以下问题:

  1. 领导服务器创建具有开始时间和结束时间的对象。开始时间和结束时间是在创建对象时设置的。

  2. 对象的开始时间设置为领导节点上的当前时间,结束时间设置为开始时间+增量时间

  3. 线程定期醒来并检查任何对象的结束时间是否小于当前时间(因此对象已过期)-如果是,则需要删除该对象

只要领导节点上的事情 运行 顺利,所有这一切都可以正常工作。如果领导节点出现故障,其中一个跟随者节点将成为新的领导者。 (leader和follower节点之间会进行复制(RAFT算法))

现在在新的leader上,时间可能和之前的leader有很大的不同。因此,步骤 3 中的计算可能会产生误导。

解决这个问题的一种方法是保持节点(领导者和追随者)的时钟同步(尽可能)。

但我想知道是否有任何其他方法可以解决分布式节点 "expiry" 的问题?

更多信息:

  1. 将使用 RAFT 协议进行消息传递和状态 复制
  2. 它将知道进程间消息延迟的界限
  3. 领导者和追随者的失败是可以容忍的(根据 RAFT 协议)
  4. 假定不会发生消息丢失(RAFT 确保这一点)
  5. 对对象的操作是检查对象是否存活。对象将由客户端排队。
  6. 进程之间会有很强的一致性(RAFT提供这个)

如果您知道您的节点同步到某个绝对时间,在某个 epsilon 内,简单的解决方案可能是将 epsilon 放入您的垃圾收集方案中。通常对于 NTP,epsilon 大约为 1ms。使用像 PTP 这样的协议,它会远低于 1 毫秒。

虽然分布式系统中并不真正存在绝对时间。尝试依赖它可能是瓶颈,因为它意味着所有节点都需要通信。避免它的一种方法,以及一般的同步,是使用 vector clock 或间隔树时钟来保持事件的相对顺序。这避免了将绝对时间作为状态同步的需要。由于序列描述了相关事件,这意味着只有具有相关事件的节点才需要进行通信。

因此,通过垃圾回收,可以使用节点序列号将对象标记为陈旧。然后,代替垃圾收集器线程检查活动性,对象可以随着序列号的增加而被收集,或者只是标记为陈旧并异步收集。

我见过以两种不同的方式完成过期。这两种方法都保证时间不会倒退,因为如果通过 NTP 同步时钟或以其他方式使用系统时钟,就会发生这种情况。特别是,这两种方法都利用芯片时钟 严格增加 时间。 (System.nanoTime 在 Javaland 中。)

这些方法过期:时间不会倒退,但有可能时间会走得更慢。

第一种方法

第一种方法可行,因为您使用的是 raft 集群(或类似协议)。它通过从领导者向副本广播一个不断增加的时钟来工作。

每个对等点都维护着我们称之为 集群时钟 的东西,它几乎实时运行。领导者通过 raft 定期广播时钟值。

当对等点接收到这个时钟值时,它会记录它,连同当前的芯片时钟值。当对等方被选为领导者时,它可以通过将其当前芯片时钟与上次记录的芯片时钟值进行比较来确定自上次时钟值以来的持续时间。

奖励 1:集群时钟值 可以 附加到每个转换,而不是具有新的转换类型,并且在安静期间领导者进行无操作转换只是为了让时钟向前移动。如果可以,请将这些附加到 raft 心跳机制中。

奖励 2:我维护了一些系统,其中 每个 转换的时间都会增加,即使在相同的时间片内也是如此。换句话说,每个转换都有一个唯一的时间戳。为了在不让时间过快地前进的情况下实现这一点,您的时钟机制必须具有能够覆盖预期转换率的粒度。毫秒只允许 1,000 tps,微秒允许 1,000,000 tps,等等

第二种方法

每个对等点仅在接收到每个对象时记录其芯片时钟并将其与每个对象一起存储。这保证了对等节点永远不会在领导者之前使对象过期,因为领导者会记录时间戳,然后通过网络发送对象。这创建了一个严格的先行关系。

但是,第二种方法容易受到服务器重启的影响。许多芯片和处理环境(例如 JVM)会在启动时将芯片时钟重置为随机值。第一种方法没有这个问题,但是比较贵。