雪花 ID / NanoID
按位段拆解雪花 ID 的时间戳与机器位,也能反解已有 ID。
这一页生成并反解雪花、NanoID、ULID 三种 ID。雪花按 Twitter 的公开描述与主流 SnowflakeIdWorker 实现打包成 1+41+5+5+12:最高位是符号位、恒 0,其后 41 位是「当前毫秒 − epoch」的时间差,再往下 10 位是节点,最右 12 位是毫秒内的序列号。NanoID 这一栏刻意不做 `random % alphabet`:取字节后按 mask=2^k−1 掩码,只接受小于字母表长度的值,所以每个字符严格等概率,字节不够就再取一轮;ULID 按 ulid/spec 是 26 字符 Crockford Base32。随机字节都来自 crypto.getRandomValues,本页不出现 Math.random。
越界字段一律当场报错而不静默截断;epoch 晚于当前时钟时时间差为负,41 位字段没有符号位,本实现拒绝发号,不回绕也不取绝对值。NanoID 的字母表长度限 2–256,有重复字符直接拒绝。反解先按长度与字符集列出候选来源并逐条标注置信度 high/medium/low——这是启发式判据,不是解析结果,能证伪的只有硬条件;NanoID 全是随机位,反解它只能证明「字符集与长度对得上」;ObjectID 与 UUID 在这页只被识别,生成与深拆在站内「UUID 生成器」「ObjectID 拆解」两页。整页没有网络请求,也没有 fetch、XHR 或埋点,不写 localStorage,取样与换算都在这台浏览器的内存里跑完;输出是可选中的普通文本,本页没有复制按钮,只留一个下载 .txt。
雪花 ID / NanoID怎么用
- 先选生成器再填参数:工具条上只有雪花/NanoID/ULID 三个开关。雪花栏填 epoch、datacenterId 与 workerId、时钟回拨策略,打开「自定义位段」才出现四个位宽框,改完 epoch 会立刻回显它是哪天、以及时间差用满后的日期;NanoID 栏填长度 1–128(默认 21)并从六个预设字母表里选(URL 64、十进制 10、十六进制 16、Base62、小写加数字 36、Crockford 32),选「自定义…」才出现填写框,「字母表体检」随之刷新;ULID 栏只有同毫秒单调递增与大小写两个开关,小写只是显示口径、解码时大小写不敏感。ULID 按 ulid/spec 是 26 字符 Crockford Base32=48 位毫秒时间(10 字符,5×10=50 位容量里最高 2 位恒 0,所以首字符只会是 0–7)+ 80 位随机(16 字符),合计 128 位;同毫秒单调递增是让 80 位随机段 +1,加到 2^80−1 就报错而不静默乱序,跨毫秒重新随机;字母表不含 I/L/O/U 是为了避开与 1/0/V 的肉眼混淆。
- 批量与边界演示:「数量」是 1–100000(默认 8),一次跑满也不会锁死标签页:每 2000 个让出一次主线程,输出区只留前 500 行并在 meta 里写明显示了几个;这个框同时被碰撞面板当成公式里的 n 试算。stats 行的累计数、实测吞吐(个/ms)、序列翻转次数与「最近一批 N 个用多少毫秒」是这一页这一次批量用 performance.now 计出来的,刷新即归零,和任何服务端能力无关。「同一毫秒硬灌 5000」把时钟钉在同一个毫秒值上连发,但回显超过约 40 行就截断,演示的是同毫秒内的序列递增;4095→0 的翻转与时间戳推进 1ms 这条分支要真实时钟跑不赢时才会出现,出现时提示条给出第一条原因,横幅还会写明本批成功几个、上限几个。生成模式下右边那栏是「最新一条的位段」:把这一批最后产出的 ID 就地拆成逐段二进制位图、右起位移(seq/worker/dc/ts)、时间差与换算出的 UTC/本地两个时刻,无需再切去反解。底部「碰撞算术」由本页自算:N=字母表长度^长度,P ≈ 1 − e^(−n(n−1)/2N),用常用对数避免溢出,逐行给 log10(N)、n(n−1)/2、P 与百分比,另给 n=10^6 的概率与 1% 碰撞所需 n≈√(2Np),以及 N=2^122(UUID v4 的随机位)、p=1e−9 时 n≈1.03e14,与 NanoID README 的「103 万亿个 v4 ID」对齐;面板参数随生成器切换(NanoID 用所选字母表与长度,ULID 固定 32 进制 16 字符即 80 位,雪花按 2 进制 126 位记账),meta 行写明用的哪一组。
- 反解与取用:切到「反解」后把 ID 粘进输入框即可,无需回车:「位段拆解」块(含逐段二进制位图与时间/节点/序列行)与「可能是谁生成的」候选表同时刷新,雪花、ULID、UUID 三种布局各自独立解一遍,解得动的都摊在同一块文本里。能解的布局全部摊开:雪花按当前参数的 epoch 与位拆分给时间差、绝对毫秒(UTC 与本地两个口径)、datacenter/worker、sequence 与逐段二进制位图;ULID 给前 10 字符的毫秒与后 16 字符的 80 位随机(补零成 20 位十六进制);UUID 只给 version 与 variant 半字节(分 RFC 9562/4122、Microsoft、reserved、NCS 四类)和 v7 前 48 位解出的毫秒时间,别的版本不替你凑时间。非 Crockford 字母报解码失败并列出非法字符,超过 2^63−1 的十进制串判为装不进 64 位雪花布局(解码器先剥掉空格、逗号、下划线和引号,再要求 1–20 位十进制,而 20 位数必然超限,所以实收 19 位以内),三样都解不动时只剩「看不出可解码的位段」加原串前 40 个字符。生成模式与反解模式共用底部那一条导出:下载 .txt 存的是当前可见文本(生成时按 snow/nano/ulid 命名,反解时存拆解结果),正文都是普通可选中文本,本页没有复制按钮。
- 雪花的位段与默认值:默认 epoch=1288834974657,即 2010-11-04T01:42:54.657Z 的 twepoch;再往下 10 位节点默认拆成 5 位 datacenter 与 5 位 worker(表单各自 0–31,默认值 1 和 9 只是演示用),最右 12 位是毫秒内序列 0–4095。默认拆法下时间戳位移是 22(=12+5+5)。换拆法要打开「自定义位段」,放开后出现四个位宽框(ts 1–62、dc/worker 0–20、seq 1–20)。时钟回拨默认照原实现抛「Clock moved backwards. Refusing to generate id for N milliseconds」,也可切成跟随新时钟并把序列清零。同一毫秒把 4095 用满后序列翻回 0,本实现把时间戳推进 1ms 再继续发,不阻塞标签页。
常见问题
两家服务用同一套雪花位段,为什么还会撞号?
撞的不是位段而是字段取值。datacenterId 与 workerId 在这页是你手填的两个 0–31 的数,页面不做任何分配、也不跨进程协调:两台机器配了同一组 (dc, worker)、又在同一毫秒发到同一个 sequence,打包出来就是同一个整数,而节点号是否唯一要由外部(配置中心、注册表、部署平台)保证,这一页给不了这个保证。时钟是第二重来源:NTP 校时或手动回调让当前毫秒回到上次发号之前时,默认策略直接拒绝(与 Twitter 原实现一致,宁可抛错也不发可能重复的 ID),选「跟随新时钟」则序列清零继续发,回拨窗口里的时间差字段就是重复的。第三重是每毫秒每节点序列只有 4096 个(12 位,0–4095),用满后本实现把时间戳推进 1ms 再继续。算术上限因此是 1024 节点 × 4096 个 = 4194304 个/毫秒,超过它只能加节点,又回到第一条的分配问题。
粘进来一个看着像 ULID 的串,为什么会被说成可能是 NanoID?
因为分类只看长度和字符集,不看内容。ULID 的硬判据是「26 字符、首字符 ≤7、每个字符都在 Crockford 表内」,而 Crockford 表里没有 I/L/O/U;NanoID 的默认字母表 A-Za-z0-9_- 四个都含,一个 26 字符的 NanoID 通常带一个非法字母,于是它落进「26 字符随机串(非 ULID)」这条 medium 而不是 ULID 的 high。反过来一个小写化的合法 ULID 会同时命中两条:字符仍然全部合法、首字符也在 0–7,但那条大写正则不认小写,两条都摆在你面前——置信标签本就是启发式的产物。能定死的只有证伪:26 个字符里混进 I/L/O/U 会当场报解码失败并列出非法字符,超过 2^63−1 的十进制串直接拒解,长度不足 26 则三样都解不动、只留一句「看不出可解码的位段」。至于解出的毫秒数是不是一个合理的时间,页面只给 UTC 与本地两个口径,不替你判断。
改掉 epoch 会影响 ID 的唯一性吗?
不会。epoch 只是时间差字段的零点:delta = 当前毫秒 − epoch,唯一性来自 dc/worker 加同毫秒序列,与零点是哪天无关。往后挪 epoch 让同一时刻的 delta 变小、生成的数字更短;往前挪则变大,一旦用满 2^41−1 ms(约 69.7 年,默认 twepoch 下页面算出的用尽日期在 2080 年 7 月)就报「时间差超出 41 位」并提示继续往后挪;挪到当前时钟之后 delta 变负,而 41 位字段没有符号位,本实现直接拒发。被悄悄改掉的是反解结果:反解不猜 epoch,它直接用参数栏里那一个(生成器切到 NanoID/ULID 时回落成 1288834974657),位拆分也跟着「自定义位段」开关走,meta 行只标出当前用的 epoch。拿别人服务的 ID 来解,epoch 差几年、解出的日期就差几年,而串本身和它的唯一性一点没变——这正是分类行写「是否真是雪花取决于 epoch 与节点位拆分」的原因。
反解的候选来源与置信度是怎么排出来的?
反解先按长度与字符集列出候选来源并逐条标注置信度 high/medium/low——这是启发式判据,不是解析结果:26 字符、首字符在 0–7、全部落在 Crockford 表内才叫 ULID(high);17–19 位纯十进制且不超过 2^63−1 给「雪花 ID(Twitter 布局)」(medium),是否真是雪花取决于 epoch 与节点位拆分;21/20/12/11 位的 URL 安全非纯数字串是 NanoID(medium);带连字符的 8-4-4-4-12 或 32 位十六进制是 UUID(high,版本号取第 13 个十六进制位);24 位十六进制是 MongoDB ObjectID(high);下划线前像对象类型缩写的「前缀_随机串」给 low;长度对得上 ULID 却含 I/L/O/U 或小写混排的给「26 字符随机串(非 ULID)」(medium)。
「字母表体检」那栏都在算什么?
「字母表体检」实时给出长度、掩码、有无重复字符、是否整除 256 与不整除时 `%len` 的偏置账——Base62(256=62×4+8)被偏爱的那 8 个字符多出 25%,十进制 10 个字符的掩码 15 要在 16 个掩码值里丢掉 6 个(约 37.5% 的字节被浪费)。默认字母表正好 64 个字符(A-Za-z0-9_-):256 整除 64、掩码 63=len−1,一个字节都不用丢、`%64` 也无偏,21 字符乘 log2(64) 是 126 位熵。
对应功能编号 #84无需安装,手机端同样可用按 Ctrl + K 换工具