#timestamp #clock #unique-identifier #hlc

无需 std uhlc

Rust 的独特混合逻辑时钟

16 个不稳定版本 (7 个破坏性更新)

0.8.0 2024年7月3日
0.7.0 2024年3月25日
0.6.3 2023年9月15日
0.6.0 2023年5月30日
0.1.0 2020年7月23日

#10 in 日期和时间

Download history 10461/week @ 2024-04-28 9094/week @ 2024-05-05 15354/week @ 2024-05-12 9708/week @ 2024-05-19 10148/week @ 2024-05-26 10313/week @ 2024-06-02 10331/week @ 2024-06-09 10977/week @ 2024-06-16 11390/week @ 2024-06-23 10365/week @ 2024-06-30 10441/week @ 2024-07-07 9686/week @ 2024-07-14 10769/week @ 2024-07-21 9790/week @ 2024-07-28 10783/week @ 2024-08-04 9302/week @ 2024-08-11

每月41,016次下载
用于 75 个 crate (18 直接)

EPL-2.0 OR Apache-2.0

55KB
880

uhlc-rs

build crate API

Rust 的独特混合逻辑时钟。

这个库是一个与唯一标识符关联的混合逻辑时钟(HLC)的实现。因此,它能够生成在分布式系统中唯一的时间戳,无需集中式时间源。

用法

将此添加到您的 Cargo.toml

[dependencies]
uhlc = "0.8"

然后在您的代码中

use uhlc::*;

// create an HLC with a random u128 and relying on SystemTime::now()
let hlc = HLC::default();

// generate a timestamp
let ts = hlc.new_timestamp();

// update the HLC with a timestamp incoming from another HLC
if ! hlc.update_with_timestamp(&other_ts).is_ok() {
    println!(r#"The incoming timestamp would make this HLC
             to drift too much. You should refuse it!"#);
}

HLC 是什么?

混合逻辑时钟结合了物理时钟和逻辑时钟。它生成接近物理时间的单调时间戳,但最后一位有一个计数器部分,允许保留“发生之前”的关系。

您可以在以下内容中找到更详细的信息:

为什么是“独特”的?

在这个实现中,每个 HLC 实例都与一个必须在系统内唯一的标识符相关联(默认为随机 u128)。每个生成的时间戳,除了混合时间外,还包含生成它的 HLC 的标识符,因此在整个系统内是唯一的。

这种属性允许对分布式系统中的所有时间戳事件进行排序,无需集中式时间源或决策。

请注意,此排序仅在事件可以关联时才保持“发生之前”的关系。也就是说。

  • 如果有两个事件来自同一个源,没问题,因为它们将由同一个 HLC 时间戳,这将生成两个有序的时间戳。

  • 如果一个实体收到一个时间戳为 t1 的事件,它必须使用 t1 更新其 HLC。因此,所有连续生成的时间戳都将大于 t1。

  • 如果有两个来自不同源的事件尚未交换过时间戳,由于每个源上的物理时钟可能没有同步,HLC 可能会生成不反映真实物理排序的时间戳。但大多数情况下这并不重要,因为这些事件之间没有真正的关联(一个不是另一个的结果)。

实现细节

操作 uhlc::uhlc::HLC::default() 会生成一个随机的 u128 作为标识符,并使用 std::time::SystemTime::now() 作为物理时钟。
但使用 uhlc::HLCBuilder 允许您以不同的方式配置 HLC。例如

let custom_hlc = HLCBuilder::new()
    .with_id(ID::try_from([0x01, 0x02, 0x03]).unwrap())     // use a custom identifier
    .with_clock(my_custom_gps_clock)                        // use a custom physical clock (e.g. using GPS as time source)
    .with_max_delta(Duration::from_secs(1))                 // use a custom maximum delta (see explanations below)
    .build();

uhlc::HLC::NTP64 时间是 64 位无符号整数,如 RFC-5909 所规定。前 32 位是物理时钟纪元以来的秒数,后 32 位是秒的分数。如果是 HLC 生成的,则第二部分的最后几位被 HLC 逻辑计数器替换。该计数器的当前大小在 uhlc::CSIZE 中硬编码为 4 位。
这给出了理论上的时间分辨率 (0xF * 10^9 / 2^32) = 3.5 纳秒。

为了避免“时钟过快”导致 HLC 在未来漂移太多,如果传入的时间戳超过当前物理时间超过一个 delta(默认为 500ms,可通过声明 UHLC_MAX_DELTA_MS 环境变量进行配置),则 uhlc::HLC::update_with_timestamp(timestamp) 操作将返回一个错误。在这种情况下,拒绝或丢弃传入的事件可能很明智,因为它可能与后续事件不正确排序。

Cargo 功能

此包提供以下 Cargo 功能

  • std:允许此包使用完整的 std。即使禁用,请注意,仍然需要 alloc 包;

  • defmt:允许相关数据结构实现 defmt::Format 特性,在 no_std 环境中使用 std::fmt::{Debug, Display} 替代进行日志记录。

默认情况下,仅启用 std 功能。

no_std 环境中的使用

为了在 no_std 环境中使用此包,应在 Cargo.toml 文件的依赖项部分添加 default-features = false 标志。与 std 实现相比,主要区别包括

  • 嵌入式环境中不存在环境变量,因此无法使用 UHLC_MAX_DELTA_MS 在运行时调整时钟“反漂移”机制的 delta。必须始终在编译时设置适当的值;

  • 通常,嵌入式系统不会跟踪“现实世界”的时间,而是在每次启动时重新初始化它们的硬件计时器。因此,在调用uhlc::HLC::default()时使用的物理时钟是一个始终返回零值时间戳的虚拟函数。由于HLC负责确保时间戳严格递增以保留“发生之前”关系,这意味着对uhlc::HLC::new_timestamp()的调用返回递增的整数;

  • 出于同样的原因,在no_std中无法进行解析为人类可读的时间格式和格式化为人类可读的时间格式;

  • 用于保证时间戳单调性的std::sync::Mutex(内部用于保证时间戳单调性)被替换为基于自旋锁而不是依赖于某些操作系统功能的spin::Mutex

  • 测试(使用cargo test)只能在std目标上运行,但根据指定的功能编译不同的代码(因此进行测试);

用法

uhlc目前用于Eclipse zenoh

依赖关系

~0.8–1.9MB
~37K SLoC