#snowflake-id #snowflake #id-generator #id #unique-id #lock-free #light-weight

snowdon

轻量级线程安全的Snowflake ID实现,适用于Rust

2个不稳定版本

0.2.0 2023年7月27日
0.1.0 2023年6月14日

并发中排名367

Download history 6/week @ 2024-06-10 4/week @ 2024-06-17 19/week @ 2024-06-24 3/week @ 2024-07-22 219/week @ 2024-07-29

每月下载量222

Apache-2.0 OR MIT

130KB
1.5K SLoC

Snowdon

Snowdon是Rust中轻量级的Snowflake ID实现。它提供了一种简单的阻塞和无锁线程安全的生成器实现。此外,它支持自定义Snowflake格式和纪元。

Snowflake IDs

Snowflake IDs是包含其“出生”时间戳(自固定纪元以来的毫秒数)和序列号的唯一ID,允许在单个毫秒内创建多个Snowflake。它们还可以包含机器或生成器ID,以实现多个生成实例之间的唯一ID而无需同步。

Twitter最初将Snowflake作为推文的ID引入。虽然它们的实现具有固定的纪元和Snowflake布局,但大多数采用Snowflake的其他服务都改变了纪元或布局(例如,使用所有42位来存储时间戳或省略机器ID)。因此,当提到“Snowflake IDs”时,我们指的是与序列号关联的时间戳,以及可选的(实例特定的)常量数据,如机器ID或Twitter的Snowflake中的前导0。

项目目标和非目标

Snowdon旨在成为一个通用的Snowflake ID实现。我们有以下目标

  • 提供Snowflake ID的正确线程安全实现。
    Snowdon生成的ID应该是唯一的、单调的,并且与其他机器在同一部署中保持一致。您可以在下面的部分中了解有关正确性的更多信息。
  • 高效生成Snowflake IDs。
    Snowflake IDs应在应用程序的任何地方工作。由于这迫使ID生成器生成无数个Snowflake,因此它必须尽可能高效。
  • 支持自定义Snowflake格式。
    与其他ID格式不同,Snowflake通常与原始实现不同,例如使用不同的纪元或更改用于存储Snowflake各个部分的位数。Snowdon使得定义和使用这些自定义格式变得容易,无需用户从头开始重新实现Snowflake。
  • 轻量级。
    Snowdon旨在适用于各种应用程序,因此保持其轻量级对于避免不必要地增加生成的二进制文件大小或编译时间至关重要。

然而,我们定义以下非目标来概述Snowdon不是什么

  • 提供针对现有软件中使用的各种类型Snowflake的具体实现。
    无数服务已采用雪花ID,其中大部分使用自定义纪元、机器ID,甚至自定义位数来表示64位整数中雪花ID的各个部分。在Snowdon中通过提供默认实现来支持特定实现,会给大多数用户不需要的库添加不必要的混乱。相反,我们的目标是尽可能简化自定义雪花格式。然而,我们确实支持结合自定义纪元的原始Twitter雪花布局,因为许多项目默认使用这种布局。
  • 提供宏或领域特定语言(DSL),以使雪花规范更容易编写。
    虽然我们可以创建宏来从自定义雪花实现中删除一些样板代码,但这会不必要地增加编译时间,而只会略微减少整体样板代码的数量,因为生成器只需要指定一次。
  • 提供多实例部署的协调。
    雪花设计为通过在每个雪花中包含机器ID来实现非协调的唯一 ID。如果必须在多生成器部署中保持ID的唯一性,用户必须在他们的雪花格式中添加唯一的生成器ID/机器ID。验证传递给Snowdon生成器的机器ID的唯一性是用户的责任。
  • 提供一种自动生成唯一机器ID的方法,例如使用机器的IP地址。
    机器ID的格式高度依赖于应用程序。大多数简单方法并不适用于每个部署,而更通用的分配机器ID的方法需要跨实例同步。因此,用户需要根据他们的应用程序推导出机器ID。
  • 提供一种动态指定雪花布局或纪元的工具。Snowdon的主要重点是创建和使用“真实”的雪花ID。其API旨在与实例范围内的常量布局和纪元一起工作,以避免在调用库中的每个函数时指定它们。但这并不意味着所有常量都必须在编译时定义。例如,在生成第一个雪花之前,可以在运行时获取应用程序的纪元。

用法

要使用此crate,只需将以下行添加到您的Cargo.toml文件中的dependencies表中

snowdon = "^0.2"

如果您想使用无锁实现,建议禁用blocking功能(默认启用),方法是禁用此crate的默认功能

snowdon = { version = "^0.2", default-features = false, features = ["lock-free"] }

要启用Serde对雪花和雪花比较器的支持,请启用serde功能

snowdon = { version = "^0.2", default-features = false, features = ["serde"] }

此库的类型旨在使其难以误用。特别是,雪花有两个类型参数表示其布局(雪花的组成——这定义了用于雪花各个部分的位数)和它们的纪元(雪花中各个时间戳所基于的时间戳)。因此,不可能在期望不同布局或纪元的API中使用雪花。

此外,此crate不提供将时间戳转换为雪花的方法。相反,它提供了一个可以直接与雪花比较的比较器类型。但是,此crate的用户不能意外地在期望雪花的地方使用这些比较器。

我们的文档包括了所有复杂方法的示例。在实现特质的函数时,请参阅函数的文档以获取详细要求和示例。

SemVer兼容性

雪顿对语义化版本兼容性非常重视。除了SemVer规范,我们保证从以0(例如0.1.1)开始的版本开始,修补版本不包含破坏性更改。即,您应该能够(自动)从版本0.1.0更新到版本0.1.1,而无需检查破坏性更改。这种行为与Cargo的SemVer兼容性相匹配。

Rust最低版本策略

我们当前支持的最小Rust版本是1.56.1。我们可能会在将来提高我们的MSRV,但我们尽量只在有充分理由的情况下这么做。由于只有少数几个具有稳定MSRV策略的依赖项,我们不需要因为依赖项更新而间接提高我们的MSRV。

请注意,基准测试和测试需要更高的Rust版本,因为它们添加了不支持上述MSRV的依赖项。

正确性

项目目标和非目标中详细说明,雪顿旨在成为Twitter的Snowflake算法的正确实现。这个crate实现的生成器应该是可以在多个线程中安全使用的。更具体地说,我们试图确保以下行为

  • 同一生成器返回的所有雪花都是唯一的。也就是说,不会有雪花被返回两次。
  • 序列号序列中没有“间隙”。如果存在序列号为1的雪花,那么也必须存在序列号为0的另一个雪花,其时间戳与序列号相同。
  • 雪花是严格单调的。如果雪花b是在雪花a之后生成的,则a < b
  • 雪顿使用当前系统时间来生成雪花。虽然这应该是一个明显的正确性要求,但部署中所有雪花生成实例都必须遵守此规则。

请注意,列表上方的“之前”和“之后”指的是实际时间。也就是说,即使系统时钟不是单调的,只要a < b,Snowdon就会在所有后续调用中只在生成雪花a之后返回雪花b

为了验证无锁算法,在spin目录中提供了一个其PROMELA实现。有关模型和我们的无锁雪花实现的信息,请参阅该目录中的README

请注意,尽管我们 尽力 提供正确且高效的实现,但并没有数学证明表明Snowdon的实现确实是正确的。我们尽最大努力确保代码正确,但如果您计划在生产环境中使用Snowdon,您应该验证我们的实现是否符合您的期望。如果您发现任何错误或潜在的效率改进,请按以下方式在CONTRIBUTING.md中描述的方式提交问题。

基准测试

Snowdon在benches中提供了基准测试。您可以使用以下命令运行它们

cargo bench --all-features

在搭载Linux 6.3.5的AMD Ryzen 9 5900X上运行测试时,我们得到了以下结果

基准测试 每个雪花平均时间(纳秒) 每秒估计的雪花ID数量
无锁生成器(顺序) 29.188 34260655
阻塞生成器(顺序) 29.038 34437633
无锁生成器(10线程) 84.954 11771076
阻塞生成器(10线程) 1594.4 627195

上表中最后两个条目表示一个极端场景,即十线程同时在一个循环中生成雪花。这是为了模拟线程之间严重的竞争,对于大多数应用,实际的雪花每秒时间应该接近顺序基准。此外,大多数实现都超过了12位序列号位的雪花布局限制。在几乎所有情况下,生成器都足以满足常规应用对ID的需求。这包括每秒需要生成超过100000个ID的应用。

许可证

版权(C)2023 Archer [email protected]

Snowdon根据您的选择许可以下之一

除非您明确声明,否则根据Apache-2.0许可证定义的任何贡献,均应作为上述双重许可证发布,不附加任何额外条款或条件。

依赖项

~0–26MB
~332K SLoC