@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.
@@ -0,0 +1,377 @@
1
+ # @cdlab996/genid
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@cdlab996/genid)](https://www.npmjs.com/package/@cdlab996/genid)
4
+ [![license](https://img.shields.io/npm/l/@cdlab996/genid)](./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)