使用 Rust 实现 SnowflakeId

在最近的业务中更改设计的时候最终决定使用 雪花 ID (下文称之为 SID)作为数据库的主键,这样可以避免使用发号器等中间件。

但是广为使用的 snowflake 的实现实际上是线程级别的唯一,而不是分布式意义上的唯一,因此在生产上如果和分布式搭配会产生极大的问题。

怎么办?只能自己写了。

原理

SID 实际上是 Rust 的 i64,他有 64 位。但是有一位是符号位,所以实际上可以使用的只有 63 位但也绰绰有余。

所以我们的结构看起来像是这样:

1
2
| sign |   data    | # sign not used.
| 1bit | 63bit |

接下来我们将会介绍标准的 SID 实现。为什么是标准的?因为存在很多变种,比如 Mastodon 就是变种 SID,我们不讨论他们。

标准的 SID 包含如下信息

  • 时间戳: 41bit
  • 标识符: 10bit
  • 序列号: 12bit

所以我们的 SID 看起来应该如下

1
2
3
| sign |                data                      |
| 0 | Timestamp | Identifier | Sequence Number |
| 1bit | 41bit | 10bit | 12bit |

✨ 深入黑暗

很好,现在我们理解了最基本的 SID 组成,让我们更深一步。

时间戳

标准的 SID 使用的是毫秒精度,恰好是 41 位。实际上根据 SID 的设计不同,时间戳可以是任意精度,也可以是任意起始位置。

标识符

在设计分布式系统时,我们会有多台机器(或实例)同时运行。

因此,我们必须区分它们。基于标识符为 10 位,我们可以同时拥有 1024 个实例,这简直太酷了!

序列号

你注意到 Sequence Number 了吗?它有 12 位,这意味着它在一毫秒内最多可以处理 4096 条信息(或其他东西,随便你)。

综上所述:整个系统在一毫秒内最多可以产生 1024 * 4096 = 4194304 条信息,这完全足够了!

分配完毕(不对,你是怎么做到的?!)

但我们总有可能遇到这样的情况:这一毫秒内的所有 SID 都已分配完毕!

此时,实例必须等待下一毫秒。在下一毫秒,我们将有新的 4096 个 SID 可以分配。

在这种情况下,可能需要拓展实例了 XD


使用 Rust 实现 SnowflakeId
https://blog.krysztal.dev/2024/10/23/使用-Rust-实现-SnowflakeId/
作者
Krysztal
发布于
2024年10月23日
许可协议