@cdlab996/genid 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -257
- package/README.zh-CN.md +377 -0
- package/dist/index.cjs +95 -240
- package/dist/index.d.cts +83 -236
- package/dist/index.d.mts +83 -236
- package/dist/index.mjs +93 -238
- package/package.json +7 -9
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# @cdlab996/genid
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@cdlab996/genid)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
基于 Snowflake 算法的高性能分布式唯一 ID 生成器,支持漂移算法和时钟回拨处理。
|
|
7
|
+
|
|
8
|
+
[English](./README.md)
|
|
9
|
+
|
|
10
|
+
## 特性
|
|
11
|
+
|
|
12
|
+
- **漂移算法** - 高并发场景下突破每毫秒序列号上限,性能更优
|
|
13
|
+
- **时钟回拨处理** - 使用保留序列号优雅降级,不阻塞 ID 生成
|
|
14
|
+
- **灵活配置** - 支持自定义时间戳、节点 ID、序列号的位长度分配
|
|
15
|
+
- **ID 验证** - 支持严格/宽松模式校验,支持 `afterTime` 时间下限约束
|
|
16
|
+
- **运行监控** - 内置统计、解析和二进制格式化调试工具
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# npm
|
|
22
|
+
npm install @cdlab996/genid
|
|
23
|
+
|
|
24
|
+
# pnpm
|
|
25
|
+
pnpm add @cdlab996/genid
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { GenidOptimized } from '@cdlab996/genid'
|
|
32
|
+
|
|
33
|
+
// 创建实例(每个 Worker/进程使用不同的 workerId)
|
|
34
|
+
const genid = new GenidOptimized({ workerId: 1 })
|
|
35
|
+
|
|
36
|
+
// 生成 ID
|
|
37
|
+
const id = genid.nextId()
|
|
38
|
+
|
|
39
|
+
// 批量生成
|
|
40
|
+
const ids = genid.nextBatch(1000)
|
|
41
|
+
|
|
42
|
+
// 解析 ID
|
|
43
|
+
const info = genid.parse(id)
|
|
44
|
+
// => { timestamp: Date, timestampMs: 1609459200000, workerId: 1, sequence: 42 }
|
|
45
|
+
|
|
46
|
+
// 验证 ID
|
|
47
|
+
genid.isValid(id) // true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
### `new GenidOptimized(options)`
|
|
53
|
+
|
|
54
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
55
|
+
| ------------------- | ------------- | :---: | ------------------ | --------------------------------------------- |
|
|
56
|
+
| `workerId` | `number` | Yes | - | 工作节点 ID(0 ~ 2^workerIdBitLength-1) |
|
|
57
|
+
| `method` | `GenidMethod` | | `DRIFT` | 算法:`DRIFT`(漂移)或 `TRADITIONAL`(传统) |
|
|
58
|
+
| `baseTime` | `number` | | `1577836800000` | 起始时间戳,毫秒(默认 2020-01-01) |
|
|
59
|
+
| `workerIdBitLength` | `number` | | `6` | 节点 ID 位数(1-15) |
|
|
60
|
+
| `seqBitLength` | `number` | | `6` | 序列号位数(3-21) |
|
|
61
|
+
| `maxSeqNumber` | `number` | | `2^seqBitLength-1` | 最大序列号 |
|
|
62
|
+
| `minSeqNumber` | `number` | | `5` | 最小序列号(0-4 保留用于时钟回拨) |
|
|
63
|
+
| `topOverCostCount` | `number` | | `2000` | 最大漂移次数 |
|
|
64
|
+
|
|
65
|
+
### 生成 ID
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
genid.nextId() // 返回 number | bigint(自动选择)
|
|
69
|
+
genid.nextNumber() // 返回 number(超出安全整数范围抛错)
|
|
70
|
+
genid.nextBigId() // 返回 bigint
|
|
71
|
+
genid.nextBatch(100) // 批量生成 100 个 ID
|
|
72
|
+
genid.nextBatch(100, true) // 批量生成 100 个 BigInt ID
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 解析与验证
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// 解析 ID 的组成部分
|
|
79
|
+
genid.parse(id)
|
|
80
|
+
// => { timestamp: Date, timestampMs: number, workerId: number, sequence: number }
|
|
81
|
+
|
|
82
|
+
// 宽松验证:检查 ID 结构是否有效
|
|
83
|
+
genid.isValid(id) // true
|
|
84
|
+
genid.isValid('invalid') // false
|
|
85
|
+
|
|
86
|
+
// 严格验证:要求 workerId 匹配当前实例
|
|
87
|
+
genid.isValid(id, true) // true(本实例生成的 ID)
|
|
88
|
+
genid.isValid(otherId, true) // false(其他实例生成的 ID)
|
|
89
|
+
|
|
90
|
+
// 时间下限验证:拒绝早于指定时间生成的 ID
|
|
91
|
+
const startupTime = Date.now()
|
|
92
|
+
genid.isValid(id, { afterTime: startupTime }) // true
|
|
93
|
+
genid.isValid(id, { strictWorkerId: true, afterTime: startupTime }) // 组合使用
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 统计与配置
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// 获取运行统计
|
|
100
|
+
genid.getStats()
|
|
101
|
+
// => {
|
|
102
|
+
// totalGenerated: 1000,
|
|
103
|
+
// overCostCount: 10,
|
|
104
|
+
// turnBackCount: 2,
|
|
105
|
+
// uptimeMs: 60000,
|
|
106
|
+
// avgPerSecond: 16,
|
|
107
|
+
// currentState: 'NORMAL' | 'OVER_COST'
|
|
108
|
+
// }
|
|
109
|
+
|
|
110
|
+
// 获取当前配置
|
|
111
|
+
genid.getConfig()
|
|
112
|
+
// => {
|
|
113
|
+
// method: 'DRIFT',
|
|
114
|
+
// workerId: 1,
|
|
115
|
+
// workerIdRange: '0-63',
|
|
116
|
+
// sequenceRange: '5-63',
|
|
117
|
+
// maxSequence: 63,
|
|
118
|
+
// idsPerMillisecond: 59,
|
|
119
|
+
// baseTime: Date,
|
|
120
|
+
// timestampBits: 52,
|
|
121
|
+
// workerIdBits: 6,
|
|
122
|
+
// sequenceBits: 6
|
|
123
|
+
// }
|
|
124
|
+
|
|
125
|
+
// 重置统计
|
|
126
|
+
genid.resetStats()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 调试
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
genid.formatBinary(id)
|
|
133
|
+
// ID: 123456789012345
|
|
134
|
+
// Binary (64-bit):
|
|
135
|
+
// 0000000000011010... - Timestamp (52 bits) = 2025-10-17T...
|
|
136
|
+
// 000001 - Worker ID (6 bits) = 1
|
|
137
|
+
// 101010 - Sequence (6 bits) = 42
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 使用示例
|
|
141
|
+
|
|
142
|
+
### 自定义位分配
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { GenidOptimized, GenidMethod } from '@cdlab996/genid'
|
|
146
|
+
|
|
147
|
+
const genid = new GenidOptimized({
|
|
148
|
+
workerId: 1,
|
|
149
|
+
method: GenidMethod.TRADITIONAL,
|
|
150
|
+
baseTime: new Date('2024-01-01').valueOf(),
|
|
151
|
+
workerIdBitLength: 10, // 支持 1024 个节点
|
|
152
|
+
seqBitLength: 12, // 每毫秒 4096 个 ID
|
|
153
|
+
topOverCostCount: 5000,
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 验证外部 ID
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// 验证从数据库或 API 获取的 ID
|
|
161
|
+
const externalId = '123456789012345'
|
|
162
|
+
if (genid.isValid(externalId)) {
|
|
163
|
+
const info = genid.parse(externalId)
|
|
164
|
+
console.log('生成时间:', info.timestamp)
|
|
165
|
+
console.log('来自节点:', info.workerId)
|
|
166
|
+
} else {
|
|
167
|
+
console.error('无效 ID')
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 性能监控
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
setInterval(() => {
|
|
175
|
+
const stats = genid.getStats()
|
|
176
|
+
console.log(`速率: ${stats.avgPerSecond} ID/s | 漂移: ${stats.overCostCount} | 回拨: ${stats.turnBackCount}`)
|
|
177
|
+
}, 10000)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 算法模式
|
|
181
|
+
|
|
182
|
+
| 模式 | 说明 | 适用场景 |
|
|
183
|
+
| ----------------- | ---------------------------------------- | -------------------- |
|
|
184
|
+
| **DRIFT**(默认) | 序列号耗尽时借用未来时间戳,避免等待 | 高频 ID 生成、高并发 |
|
|
185
|
+
| **TRADITIONAL** | 严格按时间戳递增,序列号耗尽等待下一毫秒 | 对时间顺序严格要求 |
|
|
186
|
+
|
|
187
|
+
## 架构
|
|
188
|
+
|
|
189
|
+
### ID 结构(64-bit)
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
|------------ 时间戳 ------------|-- 工作节点 ID --|-- 序列号 --|
|
|
193
|
+
42-52 bits 1-15 bits 3-21 bits
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
默认配置:时间戳 52 bits(约 139 年)| 节点 ID 6 bits(64 个节点)| 序列号 6 bits(每毫秒 59 个 ID)
|
|
197
|
+
|
|
198
|
+
序列号 `0-4` 保留用于时钟回拨,正常使用从 `5` 开始。
|
|
199
|
+
|
|
200
|
+
### 核心流程
|
|
201
|
+
|
|
202
|
+
```mermaid
|
|
203
|
+
graph TB
|
|
204
|
+
A[开始生成 ID] --> B{是否处于漂移状态?}
|
|
205
|
+
|
|
206
|
+
B -->|否| C[正常路径]
|
|
207
|
+
B -->|是| D[漂移路径]
|
|
208
|
+
|
|
209
|
+
C --> E{检测时钟}
|
|
210
|
+
E -->|时钟回拨| F[使用保留序列号 0-4]
|
|
211
|
+
E -->|时间前进| G[重置序列号]
|
|
212
|
+
E -->|同一毫秒| H{序列号是否溢出?}
|
|
213
|
+
|
|
214
|
+
H -->|否| I[序列号+1 正常生成]
|
|
215
|
+
H -->|是| J[进入漂移状态 时间戳+1]
|
|
216
|
+
|
|
217
|
+
D --> K{检测时间}
|
|
218
|
+
K -->|时间追上| L[退出漂移 恢复正常]
|
|
219
|
+
K -->|超过最大漂移| M[等待下一毫秒 退出漂移]
|
|
220
|
+
K -->|继续漂移| N{序列号是否溢出?}
|
|
221
|
+
|
|
222
|
+
N -->|否| O[使用当前序列号]
|
|
223
|
+
N -->|是| P[时间戳+1 重置序列号]
|
|
224
|
+
|
|
225
|
+
F --> Q[计算 ID]
|
|
226
|
+
G --> Q
|
|
227
|
+
I --> Q
|
|
228
|
+
J --> Q
|
|
229
|
+
L --> Q
|
|
230
|
+
M --> Q
|
|
231
|
+
O --> Q
|
|
232
|
+
P --> Q
|
|
233
|
+
|
|
234
|
+
Q --> R[更新统计]
|
|
235
|
+
R --> S[返回 ID]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## 性能
|
|
239
|
+
|
|
240
|
+
吞吐量由每毫秒可用序列号槽位数决定,增大 `seqBitLength` 可线性提升:
|
|
241
|
+
|
|
242
|
+
| seqBitLength | 每毫秒槽位 | 吞吐量 |
|
|
243
|
+
| :-----------: | ---------: | ------------------: |
|
|
244
|
+
| 3 | 3 | ~3,000 IDs/sec |
|
|
245
|
+
| 4 | 11 | ~11,000 IDs/sec |
|
|
246
|
+
| **6**(默认) | **59** | **~58,000 IDs/sec** |
|
|
247
|
+
| 8 | 251 | ~247,000 IDs/sec |
|
|
248
|
+
| 10 | 1,019 | ~1,000,000 IDs/sec |
|
|
249
|
+
| 14 | 16,379 | ~4,500,000 IDs/sec |
|
|
250
|
+
|
|
251
|
+
> 基于 Node.js v22 (x64) 测量,实际结果因环境而异。
|
|
252
|
+
> 运行 `pnpm run benchmark` 可探测当前机器的实际能力。
|
|
253
|
+
|
|
254
|
+
| 指标 | 数值(默认配置) |
|
|
255
|
+
| -------------------- | ---------------: |
|
|
256
|
+
| 最大节点数 | 64 |
|
|
257
|
+
| 时间戳可用时长 | ~139 年 |
|
|
258
|
+
| P99 延迟(单次调用) | < 1µs |
|
|
259
|
+
|
|
260
|
+
## Benchmark
|
|
261
|
+
|
|
262
|
+
内置探测脚本,自动测量当前环境的实际性能上限:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
pnpm run benchmark
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
输出内容:
|
|
269
|
+
|
|
270
|
+
1. **单次调用吞吐量** — `nextId()` 峰值 IDs/sec
|
|
271
|
+
2. **批量吞吐量** — 不同批次大小的 `nextBatch()` 性能
|
|
272
|
+
3. **延迟分位数** — P50 / P95 / P99 / P99.9 / Max
|
|
273
|
+
4. **算法对比** — DRIFT vs TRADITIONAL 并排对比
|
|
274
|
+
5. **不同 seqBitLength 吞吐量** — 位分配对吞吐量的影响
|
|
275
|
+
6. **内存占用** — 生成 100 万 ID 后的堆内存变化
|
|
276
|
+
7. **推荐阈值** — 基于峰值 60% 给出的安全测试断言值
|
|
277
|
+
|
|
278
|
+
<details>
|
|
279
|
+
<summary>输出示例</summary>
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
station :: /app/projects/genid ‹main*› » pnpm run benchmark
|
|
283
|
+
|
|
284
|
+
> @cdlab996/genid@1.4.0 benchmark /app/projects/genid
|
|
285
|
+
> npx tsx scripts/benchmark.ts
|
|
286
|
+
|
|
287
|
+
============================================================
|
|
288
|
+
GenidOptimized — Environment Capability Probe
|
|
289
|
+
Node v22.22.0 | linux x64
|
|
290
|
+
Date: 2026-04-01T08:41:07.669Z
|
|
291
|
+
============================================================
|
|
292
|
+
|
|
293
|
+
────────────────────────────────────────────────────────────
|
|
294
|
+
1. Single-call throughput (nextId)
|
|
295
|
+
────────────────────────────────────────────────────────────
|
|
296
|
+
Duration: 3001ms
|
|
297
|
+
Generated: 226,073
|
|
298
|
+
Throughput: 75,332 IDs/sec
|
|
299
|
+
|
|
300
|
+
────────────────────────────────────────────────────────────
|
|
301
|
+
2. Batch throughput (nextBatch)
|
|
302
|
+
────────────────────────────────────────────────────────────
|
|
303
|
+
batch= 100 × 5000 => 61,665 IDs/sec
|
|
304
|
+
batch= 1,000 × 500 => 61,577 IDs/sec
|
|
305
|
+
batch= 10,000 × 50 => 61,716 IDs/sec
|
|
306
|
+
batch=100,000 × 5 => 61,644 IDs/sec
|
|
307
|
+
|
|
308
|
+
────────────────────────────────────────────────────────────
|
|
309
|
+
3. Single-call latency percentiles
|
|
310
|
+
────────────────────────────────────────────────────────────
|
|
311
|
+
Samples: 100,000
|
|
312
|
+
Avg: 0µs
|
|
313
|
+
P50: 0µs
|
|
314
|
+
P95: 1µs
|
|
315
|
+
P99: 1µs
|
|
316
|
+
P99.9: 1µs
|
|
317
|
+
Max: 364µs
|
|
318
|
+
|
|
319
|
+
────────────────────────────────────────────────────────────
|
|
320
|
+
4. Algorithm comparison (DRIFT vs TRADITIONAL)
|
|
321
|
+
────────────────────────────────────────────────────────────
|
|
322
|
+
DRIFT 58,296 IDs/sec drift=1
|
|
323
|
+
TRADITIONAL 59,000 IDs/sec drift=0
|
|
324
|
+
|
|
325
|
+
────────────────────────────────────────────────────────────
|
|
326
|
+
5. Throughput by sequence bit length
|
|
327
|
+
────────────────────────────────────────────────────────────
|
|
328
|
+
seqBits= 3 maxSeq= 7 => 2,986 IDs/sec drift=1
|
|
329
|
+
seqBits= 4 maxSeq= 15 => 10,884 IDs/sec drift=1
|
|
330
|
+
seqBits= 6 maxSeq= 63 => 58,240 IDs/sec drift=1
|
|
331
|
+
seqBits= 8 maxSeq= 255 => 247,804 IDs/sec drift=1
|
|
332
|
+
seqBits=10 maxSeq= 1023 => 1,008,930 IDs/sec drift=1
|
|
333
|
+
seqBits=14 maxSeq=16383 => 4,130,701 IDs/sec drift=0
|
|
334
|
+
|
|
335
|
+
────────────────────────────────────────────────────────────
|
|
336
|
+
6. Memory footprint
|
|
337
|
+
────────────────────────────────────────────────────────────
|
|
338
|
+
Generated: 1,000,000 IDs (not stored)
|
|
339
|
+
Heap delta: -6.27 MB
|
|
340
|
+
Note: Run with --expose-gc for accurate GC-forced measurement
|
|
341
|
+
|
|
342
|
+
────────────────────────────────────────────────────────────
|
|
343
|
+
Summary — Recommended test thresholds
|
|
344
|
+
────────────────────────────────────────────────────────────
|
|
345
|
+
Peak single-call: 75,332 IDs/sec
|
|
346
|
+
Peak batch: 61,716 IDs/sec
|
|
347
|
+
Suggested min threshold: 45,199 IDs/sec (60% of peak)
|
|
348
|
+
Suggested batch threshold: 37,029 IDs/sec (60% of peak)
|
|
349
|
+
Suggested P99 cap: 2µs (3× measured P99)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
</details>
|
|
353
|
+
|
|
354
|
+
## 开发
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
pnpm install # 安装依赖
|
|
358
|
+
pnpm run build # 构建(ESM + CJS)
|
|
359
|
+
pnpm run dev # 监听模式
|
|
360
|
+
pnpm run test # 运行测试
|
|
361
|
+
pnpm run benchmark # 运行环境性能探测
|
|
362
|
+
pnpm run typecheck # 类型检查
|
|
363
|
+
pnpm run lint # 代码检查(Biome)
|
|
364
|
+
pnpm run format # 代码格式化(Biome)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## 注意事项
|
|
368
|
+
|
|
369
|
+
- 每个 Worker/进程必须使用**不同的 workerId**
|
|
370
|
+
- 实例**非线程安全**,不要跨线程共享
|
|
371
|
+
- `workerIdBitLength + seqBitLength` 不能超过 22
|
|
372
|
+
- 序列号 0-4 保留用于时钟回拨处理
|
|
373
|
+
- 超出 JavaScript 安全整数范围(2^53-1)时,使用 `nextBigId()` 或 `nextId()`(自动返回 BigInt)
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
[MIT](./LICENSE) License © 2025-PRESENT [wudi](https://github.com/WuChenDi)
|