1. 标识符概述

聊样本唯一标识之前,我先问个问题:你手头有一百万个样本数据,怎么保证每一个都能被精准定位,不重复、不丢失?最笨的办法是写个自增序号,可一旦跨系统、跨节点,这个序号就彻底失控了。我在第一个分布式项目里踩过这个坑——两个服务各自生成了“1号”,合并数据时乱成一锅粥。从那以后我才明白:唯一标识不是锦上添花,是系统的基础设施。

什么是样本唯一标识

说白了,就是一个字符串或数字,能唯一代表一个样本对象。它在全局范围内(至少在你的系统scope内)不会重复。常见的样本标识有:

  • 生信领域的 sample_id
  • 电商订单的 order_no
  • IoT 设备的 device_uid

你需要用它来去重、关联元数据、做溯源。我习惯把标识符看作样本的“身份证号码”——必须有且只有一张。

核心特性:一个合格的标识符,必须满足唯一性和可定性。唯一性保证不冲突,可定性保证你能根据标识符知道它从哪来、何时生成。

标识符的维度

设计标识符不是拍脑袋选个算法就行。你需要从几个关键维度去权衡:

维度 说明 典型需求
全局唯一 无论多少个节点、多少年,不能出现两个相同的ID 分布式系统、跨库数据合并
趋势递增 后生成的ID比先生成的ID数值大,方便数据库B+树索引 MySQL InnoDB主键、Elasticsearch排序
无序/随机 ID看不出生成顺序,避免泄露数据量 隐私敏感场景、URL可猜测性防御
高性能 生成速度要快,高并发下不能成为瓶颈 每秒数万级别的插入
可读性 人能看懂部分信息(如时间、机器ID) 运维排查、日志审计

你看,这五个维度往往互相矛盾。比如全局唯一 + 趋势递增通常意味着带时间戳,但时间戳会泄露信息。我曾在金融项目里同时要求不可泄露和可排序,最后选了改造版雪花算法。

我的经验:先确定你业务里最不能妥协的维度。如果是传统关系型数据库,优先趋势递增;如果是分布式日志系统,优先全局唯一和高性能。

常见方案对比

我们来看目前工业界最常用的几种方案。我整理了它们的核心差异:

方案 唯一性保证 趋势递增 生成性能 可读性 典型长度
UUID v4(随机) 全局唯一 否(完全随机) 高(本地生成) 低 36字符(含短横)
Snowflake 算法 全局唯一 是(按时间递增) 极高(内存运算) 中(可解码时间) 19位十进制或64位整数
数据库自增ID 单库唯一 是 中等(依赖DB) 高 int/bigint
Redis INCR 全局唯一(单点) 是 高(内存操作) 高 整数
ULID 全局唯一 是(按时间排序) 高 较高(Base32编码) 26字符

你说UUID不好吗?它确实不需要协调,各节点独立生成。但你看它的长度——36个字符,当主键索引时,B+树的扇出直接坍缩,磁盘IO暴涨。我曾经在生产环境里用UUID做主键,查询性能比自增ID慢了3倍以上。

Snowflake 是 Twitter 开源的方案。原理很简单:

| 符号位(1) | 时间戳(41) | 机器ID(10) | 序列号(12) |

总长64位,可保证 4096/毫秒/机器的并发量。我在公司内部自研的ID生成器就是基于Snowflake,只改了机器ID的分配策略——用ZK协调取代手工配置。

注意:Snowflake 对系统时钟非常敏感。如果你做过NTP时间回拨,序列号可能重复。我建议在回拨发生时等待或借用未来时间戳。

数据库自增ID更简单,但只适用于单体或分库分表后通过设置步长来避免冲突。像这样:

-- 分库1:自增从1开始,步长2
CREATE TABLE t1 (id BIGINT AUTO_INCREMENT PRIMARY KEY) AUTO_INCREMENT=1;
-- 分库2:自增从2开始,步长2
CREATE TABLE t2 (id BIGINT AUTO_INCREMENT PRIMARY KEY) AUTO_INCREMENT=2;

这样能保证ID不重复,但扩容时步长需要重新计算,非常麻烦。我建议只在初期使用。

ULID 是一个不错的折中方案。它保留了时间前缀,可以按时间排序,又使用Crockford Base32编码,避免了相似字符混淆。生成示例:

# 安装 ulid-py
pip install ulid-py

import ulid
uid = ulid.new()
print(str(uid))  # 输出类似:01ARZ3NDEKTSV4RRFFQ69G5FAV

它的前10个字符是毫秒级时间戳,后16个字符是随机数。既保证了趋势递增,又不会泄露精确时间。

唯一标识符设计维度与方案 唯一标识符 全局唯一 趋势递增 高性能 可读性 UUID v4 ULID Snowflake 自增ID Redis INCR 自定义哈希 每种方案在不同维度上各有取舍,没有银弹 需要根据业务场景选择平衡点

看到这个图了吗?每个方案都能覆盖某些维度,但没有一个能全满。比如说,UUID v4 全局唯一做得最好,但完全无序、长度又长。Snowflake 在趋势递增和高性能上表现优异,但依赖时钟。我个人的选择逻辑很简单:

  • 单体应用:直接用数据库自增ID,省心。
  • 中小分布式:Snowflake 或 ULID,看你对可读性的要求。
  • 高并发海量节点:Snowflake + 自定义机器ID分配,或者直接上 UUID v7(时间有序UUID)。
  • 隐私敏感:UUID v4 或 哈希值,避免可猜测。

我曾经在一个跨境电商项目里,需要每秒生成5万个订单号,同时要求后期能按时间范围查询。最终选了Snowflake,并且把机器ID写死在配置中心——运维同学每次扩容时在ZK上注册一个节点,生成器自动获取可用ID。线上跑了两年,零冲突。

总结一下:设计样本唯一标识,先理清你必须满足哪些维度,再对比方案。不要一开始就陷入代码细节。记住:标识符是基础设施,一旦上线很难更换。