7 个版本
0.1.6 | 2023年6月14日 |
---|---|
0.1.5 | 2022年4月1日 |
0.1.4 | 2021年12月21日 |
0.1.3 | 2021年11月18日 |
#245 in 开发工具
每月479次下载
32KB
464 行
tiny_id
tiny_id 已被 block-id
取代。 block-id
在分布式环境中的使用具有更好的属性,并且是可逆的。对于新用途,我建议使用 block-id
而不是 tiny_id
。
tiny_id
是一个用于生成非顺序、紧密排列的短ID的 Rust 库。
大多数其他的短ID生成器只是将随机数字连接起来。由于 生日问题,这种方法容易发生冲突。例如,一个四位的字母代码在800个代码后大约有50%的冲突概率。
tiny_id
使用 线性同余生成器 生成代码,这些代码不会发生冲突,同时只保留一小部分恒定大小的状态。对于相同的四位字母代码,tiny_id
在所有456,976个可能的代码都生成之前,冲突概率为0%。
这些代码适用于需要短、可读的代码的场景,使得连续生成的两个代码与不连续生成的代码一样不可能相似。它不应在需要不可猜测的代码的情况下使用。它们也不能防止有人足够有动机进行 德国坦克问题 类型的分析。
随机短代码的示例,由 generate.rs 示例生成,展示了这些代码的序列看起来像什么
paul:~/tiny_id$ target/debug/examples/generate 36 4 10
9chl
yqpv
z1rk
c18m
1tc5
2t22
ftgv
4yih
5nag
ioa0
如何使用它
基本用法
use tiny_id::ShortCodeGenerator;
fn main() {
// The length of generated codes
let length: usize = 6;
// Create a generator. The generator must be mutable, because each
// code generated updates its state.
let mut generator = ShortCodeGenerator::new_alphanumeric(length);
// Generate the next short code, and update the internal generator state.
let code = generator.next_string();
assert_eq!(length, code.len());
}
字母表
用于构建短代码的字符集(或任意值)被称为 字母表。 tiny_id
有几个内置的字母表,或者您可以提供自己的。
如果您提供字母表而不是使用内置的字母表,该字母表必须不包含任何重复条目。库不会强制执行此要求,但违反此要求会导致冲突。
use tiny_id::ShortCodeGenerator;
fn main() {
let length: usize = 6;
// There are several built-in alphabets with convenience constructors.
// Numeral digits (0-9), like "769458".
ShortCodeGenerator::new_numeric(length);
// Numeral digits and lowercase letters (a-z), like "l2sx2b".
ShortCodeGenerator::new_lowercase_alphanumeric(length);
// Numeral digits, lowercase, and uppercase letters, like "qAh6Gg".
ShortCodeGenerator::new_alphanumeric(length);
// Uppercase letters only, like "MEPQOD".
ShortCodeGenerator::new_uppercase(length);
// You can also provide an alphabet with any unicode characters.
// I hope you don't use it for emoji, but you could:
ShortCodeGenerator::with_alphabet(
"😛🐵😎".chars().collect(),
length
);
// The generator can also be used with non-char types, as long
// as they implement Copy.
let mut gen = ShortCodeGenerator::with_alphabet(
vec![true, false],
length
);
// next_string() is only implemented on ShortCodeGenerator<char>,
// but gen is a ShortCodeGenerator<bool>, so we need to call
// next_vec() instead, which returns a Vec<bool>.
let result: Vec<bool> = gen.next_vec();
assert_eq!(length, result.len());
}
耗尽策略
最终,所有短代码生成器都会达到一个点,即它们用完了给定长度的代码。当发生这种情况时,有以下三种选择来处理:
- 增加长度。这对应于
ExhaustionStrategy::IncreaseLength
,这是默认值。 - 循环。这会从开始处重复代码的循环。每个循环中代码的顺序是相同的。对应于
ExhaustionStrategy::Cycle
。 - 恐慌。在“快速失败”的精神下,当所有代码都已使用时,它会触发恐慌,对于预期之外耗尽的情况,其他程序假定不会发生。对应于
ExhaustionStrategy::Panic
。
use tiny_id::{ShortCodeGenerator, ExhaustionStrategy};
fn main() {
// Increase length (default).
let mut gen = ShortCodeGenerator::new_uppercase(2);
for _ in 0..(26*26) {
let result = gen.next_vec();
assert_eq!(2, result.len());
}
// We've exhausted all options, so our next code will be longer.
let result = gen.next_vec();
assert_eq!(3, result.len());
// Cycle.
let mut gen = ShortCodeGenerator::new_uppercase(2)
.exhaustion_strategy(ExhaustionStrategy::Cycle);
let first = gen.next_vec();
for _ in 0..(26*26-1) {
let result = gen.next_vec();
assert_eq!(2, result.len())
}
// We've exhausted all options, so our next code will be a
// repeat of the first.
let result = gen.next_vec();
assert_eq!(first, result);
}
工作原理
线性同余发生器(LCG)是一种简单、非加密的伪随机数生成器。
LCGs很有趣,因为它们在循环中生成数字,并且LCG的循环长度作为参数的函数已被广泛研究。特别是,人们已经很好地理解了如何创建一个生成1..m的数字且循环长度为m的LCG,即生成1..m的数字排列。这被称为Hull-Dobell定理。
对于大小为N
的字母表和长度为L
的ID,有N ^ L
种可能的代码。我们可以通过将代码视为数字的base-N
表示来在数字和这些代码之间相互转换。
结合这两个事实,我们的方法是
- 利用Hull-Dobell定理,构建一个LCG,使其可以以某种随机(类似)的顺序“访问”从
1
到N^L
的所有数字。 - 使用基转换方法,将这些数字转换为短ID。
注意事项
请注意,ShortCodeGenerator
对象本身包含少量状态,每次生成短代码时都会更新。必须使用同一个ShortCodeGenerator
对象生成短代码,以避免冲突。
使用默认启用的serde
crate选项,ShortCodeGenerator
对象可以被序列化为serde支持的任何格式。这可以用于将生成器的状态持久化以供以后使用。(如果你使用的是自定义字母表,该字母表类型也必须是可序列化的。)
可能的代码总数(字母表大小到长度的幂)必须适合于一个u64。如果你处理足够大的代码和字母表,这可能会成为问题(因为随机碰撞会更常见)。
随机性仅在构建ShortCodeGenerator
时使用。代码生成本身完全基于当前生成器的状态。
所有操作都使用常数时间和空间,除了ShortCodeGenerator
的构建。构建在技术上具有超线性的时间复杂度,与字母表基数的大小相关。对于合理的字母表大小(例如,<1000),这应该微不足道。
分区
如果你需要从多个生成器生成短代码,可以使用内置的分区功能来生成多个短代码生成器,这些生成器从代码空间的不重叠分区中生成代码。
内部,这是通过创建 num_partitions
个生成器的副本来实现的,在每个副本上“烧录”从 0..num_partitions
的唯一编号,然后将每个副本设置为跳过它生成的每个编号中的 num_partitions - 1
个编号。
一旦 ShortCodeGenerator
被分区,就不能再次分区或使用不同数量的分区重新分区。一旦分区,生成器可以被序列化以发送到不同的机器或存储以供以后使用。
use tiny_id::{ShortCodeGenerator, ExhaustionStrategy};
fn main() {
// This returns a vector of two short code generators.
let mut generators: Vec<ShortCodeGenerator<_>> = ShortCodeGenerator::new_uppercase(5)
.into_partitioned_generators(2);
// Codes generated by the two generators will never overlap, because
// they come from separate code spaces.
assert_ne!(generators[0].next_string(), generators[1].next_string());
}
替代方案
- 如果您只需要唯一的标识符且不关心它们的长度,请使用 uuid。它的优点是不需要任何状态。
- 如果您已经分配了唯一的整数(例如 SQL 中的
AUTO INCREMENT
)且不关心它们是否连续,您可以直接将这些整数转换为字符串并使用基数转换回整数。
依赖项
~0.4–1MB
~18K SLoC