| Crates.io | kvid |
| lib.rs | kvid |
| version | 0.1.6 |
| created_at | 2025-11-22 05:24:11.875956+00 |
| updated_at | 2025-12-23 03:06:48.046879+00 |
| description | Distributed ID generator with dual-segment preloading and lock-free fast path / 双号段预加载、无锁快速路径的分布式 ID 生成器 |
| homepage | https://github.com/js0-site/rust/tree/main/kvid |
| repository | https://github.com/js0-site/rust.git |
| max_upload_size | |
| id | 1944837 |
| size | 93,424 |
kvid is a distributed unique ID generator based on Redis/Kvrocks. It uses dual-segment preloading with lock-free fast path to achieve high throughput and low latency.
HINCRBY ensures no duplicate IDs across distributed nodesstatic variable with const_newuse kvid::KvId;
// declare as static global / 声明为静态全局变量
static USER_ID: KvId = KvId::const_new("user");
async fn create_user() -> kvid::Result<u64> {
xboot::init().await?;
USER_ID.next().await
}
Concurrent usage:
use std::time::Duration;
use kvid::{KVID_KEY, KvId};
use fred::interfaces::HashesInterface;
use xkv::R;
static KVID_TEST: KvId = KvId::const_new("test");
async fn demo() -> kvid::Result<()> {
xboot::init().await?;
let t1 = tokio::spawn(async {
for _ in 0..50 {
let id = KVID_TEST.next().await?;
println!("t1: {id}");
tokio::time::sleep(Duration::from_millis(10)).await;
}
Ok::<_, kvid::Error>(())
});
let t2 = tokio::spawn(async {
for _ in 0..50 {
let id = KVID_TEST.next().await?;
println!("t2: {id}");
tokio::time::sleep(Duration::from_millis(10)).await;
}
Ok::<_, kvid::Error>(())
});
t1.await.unwrap()?;
t2.await.unwrap()?;
// cleanup / 清理
R.hdel::<(), _, _>(KVID_KEY, "test").await?;
Ok(())
}
| Constant | Value | Description |
|---|---|---|
PRELOAD_SEC |
60 | Target duration (seconds) for segment |
STEP_MIN |
1 | Minimum step size |
STEP_MAX |
1,000,000 | Maximum step size |
KVID_KEY |
"kvid" | Redis hash key |
Main struct for ID generation.
// const initialization for static / 静态变量用 const 初始化
pub const fn const_new(name: &'static str) -> Self
// runtime initialization / 运行时初始化
pub fn new(name: impl Into<SmolStr>) -> Self
// generate next ID / 生成下个 ID
pub async fn next(&'static self) -> Result<u64>
pub enum Error {
Empty, // segment exhausted unexpectedly
StepOverflow(u64), // step exceeds i64 range
Kv(fred::error::Error), // Redis/Kvrocks error
}
graph TD
subgraph "KvId struct"
KvId["KvId { name, fast, slow }"]
end
subgraph "Fast (lock-free)"
Fast["Fast { id: AtomicU64, max: AtomicU64, lock: AtomicBool }"]
end
subgraph "Slow (Mutex protected)"
Slow["Slow { next: Option<Seg>, step: u64, ts: u64 }"]
end
KvId --> Fast
KvId --> Slow
graph TD
A["next()"] --> B["try_next()"]
B --> C{"fast.id < fast.max?"}
C -->|Yes| D["CAS: fast.id += 1"]
D --> E["return Ok(id)"]
D --> F{"fast.lock == false?"}
F -->|Yes| G["spawn_fill()"]
C -->|No| H["slow.lock()"]
H --> I["retry try_next()"]
I -->|Success| E
I -->|Fail| J{"slow.next.is_some()?"}
J -->|Yes| K["set_seg(slow.next.take())"]
K --> L["try_next()"]
L --> M["spawn_fill()"]
M --> E
J -->|No| N["calc_step()"]
N --> O["fetch(step)"]
O --> P["HINCRBY kvid name step"]
P --> Q["set_seg(Seg{id, max})"]
Q --> R["update slow.step, slow.ts"]
R --> S["try_next()"]
S --> T["spawn_fill()"]
T --> E
graph TD
A["spawn_fill()"] --> B{"fast.lock.swap(true)?"}
B -->|Already locked| C["return"]
B -->|Got lock| D["tokio::spawn fill()"]
D --> E["calc_step()"]
E --> F["fetch(step)"]
F --> G{"slow.next.is_none()?"}
G -->|Yes| H["slow.next = Some(seg)"]
G -->|No| I["discard"]
H --> J["fast.lock = false"]
I --> J
Fast (lock-free):
id: AtomicU64 - current allocated IDmax: AtomicU64 - segment upper boundlock: AtomicBool - fill lock to prevent duplicate prefetchSlow (mutex protected):
next: Option<Seg> - buffered segmentstep: u64 - current batch sizets: u64 - last fetch timestampnew_step = prev_step * PRELOAD_SEC / elapsed
new_step = clamp(new_step, STEP_MIN, STEP_MAX)
High load → larger step → fewer network calls Low load → smaller step → less ID waste
HSET kvid {name} {max_id}
HINCRBY kvid {name} {step}
.
├── Cargo.toml
├── src/
│ ├── lib.rs # public API, KvId struct
│ ├── impl.rs # core implementation
│ └── error.rs # error definitions
└── tests/
└── main.rs # integration tests
kvid advantages: Rust implementation, lock-free fast path, dual-segment preloading, static global support.
| Algorithm | Pros | Cons |
|---|---|---|
| UUID | No coordination, simple | 128-bit, unordered, bad for indexing |
| DB Auto-increment | Simple, strictly ordered | Single point failure, hard to scale |
| Snowflake | High perf, time-ordered, local | Clock dependency, machine ID management |
| Segment (kvid) | No clock dependency, trend increasing | ID gaps on restart, central store dependency |
Distributed ID generation emerged as web applications scaled beyond single databases. Twitter's Snowflake (2010) pioneered time-based ID generation but suffered from clock dependency. Flickr's Ticket Server introduced segment-based allocation, later refined by Meituan Leaf.
The segment approach trades small ID gaps for clock independence. kvid advances this pattern with Rust's zero-cost abstractions: lock-free fast path handles most requests, while dual-segment preloading eliminates blocking waits. The result is microsecond-level latency with guaranteed uniqueness.
Fun fact: Redis HINCRBY, the atomic operation kvid relies on, was added in Redis 2.0 (2010) - the same year Snowflake was released. Both solutions emerged from the same era of distributed systems challenges.
This project is an open-source component of js0.site ⋅ Refactoring the Internet Plan.
We are redefining the development paradigm of the Internet in a componentized way. Welcome to follow us:
kvid 是基于 Redis/Kvrocks 的分布式唯一 ID 生成器。采用双号段预加载 + 无锁快速路径设计,实现高吞吐、低延迟的 ID 分配。
HINCRBY 确保分布式节点间无重复 IDconst_new 声明为 static 变量use kvid::KvId;
// declare as static global / 声明为静态全局变量
static USER_ID: KvId = KvId::const_new("user");
async fn create_user() -> kvid::Result<u64> {
xboot::init().await?;
USER_ID.next().await
}
并发使用:
use std::time::Duration;
use kvid::{KVID_KEY, KvId};
use fred::interfaces::HashesInterface;
use xkv::R;
static KVID_TEST: KvId = KvId::const_new("test");
async fn demo() -> kvid::Result<()> {
xboot::init().await?;
let t1 = tokio::spawn(async {
for _ in 0..50 {
let id = KVID_TEST.next().await?;
println!("t1: {id}");
tokio::time::sleep(Duration::from_millis(10)).await;
}
Ok::<_, kvid::Error>(())
});
let t2 = tokio::spawn(async {
for _ in 0..50 {
let id = KVID_TEST.next().await?;
println!("t2: {id}");
tokio::time::sleep(Duration::from_millis(10)).await;
}
Ok::<_, kvid::Error>(())
});
t1.await.unwrap()?;
t2.await.unwrap()?;
// cleanup / 清理
R.hdel::<(), _, _>(KVID_KEY, "test").await?;
Ok(())
}
| 常量 | 值 | 说明 |
|---|---|---|
PRELOAD_SEC |
60 | 号段目标持续时长(秒) |
STEP_MIN |
1 | 最小步长 |
STEP_MAX |
1,000,000 | 最大步长 |
KVID_KEY |
"kvid" | Redis 哈希键 |
ID 生成器主结构体。
// const initialization for static / 静态变量用 const 初始化
pub const fn const_new(name: &'static str) -> Self
// runtime initialization / 运行时初始化
pub fn new(name: impl Into<SmolStr>) -> Self
// generate next ID / 生成下个 ID
pub async fn next(&'static self) -> Result<u64>
pub enum Error {
Empty, // 号段意外耗尽
StepOverflow(u64), // 步长超出 i64 范围
Kv(fred::error::Error), // Redis/Kvrocks 错误
}
graph TD
subgraph "KvId 结构体"
KvId["KvId { name, fast, slow }"]
end
subgraph "Fast 无锁"
Fast["Fast { id: AtomicU64, max: AtomicU64, lock: AtomicBool }"]
end
subgraph "Slow 互斥锁保护"
Slow["Slow { next: Option<Seg>, step: u64, ts: u64 }"]
end
KvId --> Fast
KvId --> Slow
graph TD
A["next()"] --> B["try_next()"]
B --> C{"fast.id < fast.max?"}
C -->|是| D["CAS: fast.id += 1"]
D --> E["return Ok(id)"]
D --> F{"fast.lock == false?"}
F -->|是| G["spawn_fill()"]
C -->|否| H["slow.lock()"]
H --> I["重试 try_next()"]
I -->|成功| E
I -->|失败| J{"slow.next.is_some()?"}
J -->|是| K["set_seg(slow.next.take())"]
K --> L["try_next()"]
L --> M["spawn_fill()"]
M --> E
J -->|否| N["calc_step()"]
N --> O["fetch(step)"]
O --> P["HINCRBY kvid name step"]
P --> Q["set_seg(Seg{id, max})"]
Q --> R["更新 slow.step, slow.ts"]
R --> S["try_next()"]
S --> T["spawn_fill()"]
T --> E
graph TD
A["spawn_fill()"] --> B{"fast.lock.swap(true)?"}
B -->|已锁定| C["return"]
B -->|获取锁| D["tokio::spawn fill()"]
D --> E["calc_step()"]
E --> F["fetch(step)"]
F --> G{"slow.next.is_none()?"}
G -->|是| H["slow.next = Some(seg)"]
G -->|否| I["丢弃"]
H --> J["fast.lock = false"]
I --> J
Fast (无锁):
id: AtomicU64 - 当前已分配 IDmax: AtomicU64 - 号段上界lock: AtomicBool - 填充锁,防止重复预取Slow (互斥锁保护):
next: Option<Seg> - 缓冲号段step: u64 - 当前批量大小ts: u64 - 上次获取时间戳new_step = prev_step * PRELOAD_SEC / elapsed
new_step = clamp(new_step, STEP_MIN, STEP_MAX)
高负载 → 步长增大 → 减少网络调用 低负载 → 步长减小 → 减少 ID 浪费
HSET kvid {name} {max_id}
HINCRBY kvid {name} {step}
.
├── Cargo.toml
├── src/
│ ├── lib.rs # 公开 API,KvId 结构体
│ ├── impl.rs # 核心实现
│ └── error.rs # 错误定义
└── tests/
└── main.rs # 集成测试
kvid 优势:Rust 实现、无锁快速路径、双号段预加载、静态全局支持。
| 算法 | 优点 | 缺点 |
|---|---|---|
| UUID | 无需协调,简单 | 128 位,无序,索引性能差 |
| 数据库自增 | 简单,严格有序 | 单点故障,难扩展 |
| Snowflake | 高性能,时间有序,本地生成 | 时钟依赖,机器 ID 管理 |
| 号段模式 (kvid) | 无时钟依赖,趋势递增 | 重启有 ID 空洞,依赖中心存储 |
分布式 ID 生成随 Web 应用规模扩张而兴起。Twitter 的 Snowflake (2010) 开创了基于时间戳的 ID 生成,但受制于时钟依赖。Flickr 的 Ticket Server 引入号段分配模式,后被美团 Leaf 发扬光大。
号段模式以微小的 ID 空洞换取时钟无关性。kvid 借助 Rust 零成本抽象推进这一模式:无锁快速路径处理绝大多数请求,双号段预加载消除阻塞等待,实现微秒级延迟与唯一性保证。
趣闻:kvid 依赖的 Redis HINCRBY 命令在 Redis 2.0 (2010) 中引入——与 Snowflake 发布同年。两种方案诞生于分布式系统挑战的同一时代。
本项目为 js0.site ⋅ 重构互联网计划 的开源组件。
我们正在以组件化的方式重新定义互联网的开发范式,欢迎关注: