无标题文档
来源:无标题文档
📎 distributed-id-infographic-light.html.html
口播文案:
简历上写着 “精通分布式 ID 生成,用 UUID 搞定全局唯一标识,支撑千万级订单系统”,面试官抬了抬眼:“行,那咱聊聊你们的 UUID。我听说你们上线半个月,出现了两个一模一样的订单 ID,用户付了两次钱,最后只能手动退款 —— 这事儿你咋解释?”
你愣了愣:“不可能啊!UUID 不是全球唯一吗?怎么会撞车?”
面试官冷笑:“全球唯一是理论上的,实际呢?再问你,你们用 UUID 当订单表主键,数据量到 100 万的时候,查询一条订单从 0.2 秒涨到 0.8 秒,知道为啥不?”
你挠挠头:“是不是… 索引没建好?”
- 追问:“我再问你,大促的时候每秒要生成 5 万个订单 ID,你们用 UUID v4,结果生成速度跟不上,订单接口超时率飙到 10%,这性能问题咋来的?”
- 再逼:“你说换雪花算法,那要是服务器时钟回拨了 300 毫秒,生成的 ID 会不会重复?所有服务器都回拨,你咋兜底?”
- 最后补刀:“选 ID 生成方案前,你算过 ID 长度吗?比如用 INT 自增,用户量超 21 亿了咋办?总不能中途改字段类型吧?”
这就是你说的 “精通”?今天咱就从 UUID 的坑聊起,把所有 ID 生成方案扒明白,看看你到底踩了多少没注意的雷。
首先得说:你以为的 UUID “稳如老狗”,其实全是坑。咱先搞清楚 UUID 到底是啥 —— 它就是个 128 位的数字,写成 36 位字符串(比如 550e8400-e29b-41d4-a716-446655440000),常用的是 v1 和 v4。
v1 靠 “时间戳 + MAC 地址”,MAC 地址会暴露服务器硬件信息,不安全;v4 靠纯随机数,看似安全,可你知道它为啥会撞车吗?官方说碰撞概率比 “中两次五百万 + 被雷劈三次” 还低,但架不住你用的 UUID 库有 bug 啊!我之前遇过一个团队,用的某语言库随机数种子没做好,高并发下每秒生成几千个,三个月就撞了 —— 等发现的时候,库里已经躺了一堆重复 ID 的脏数据,清理到半夜。
更坑的是性能。你说 UUID 生成快?我之前测过,Java 里生成 100 万 ID,UUID v4 要 128 毫秒,雪花算法才 19 毫秒,差了 7 倍!为啥?因为随机数生成本身就耗时,而且 UUID 是字符串,存数据库的时候,字符串索引比数字索引慢 30% 以上,数据量越大,差距越明显。
还有个硬伤:无序。数据库索引(比如 InnoDB)是按顺序排的,自增 ID 新的比旧的大,索引不用动;但 UUID 是随机的,新 ID 可能比旧的小,数据库就得不停调整索引,经常会“页分裂”,时间长了索引碎片化,查询能快吗?我之前维护过一个 UUID 主键的表,100 万数据时查询慢到 0.8 秒,换成自增 ID 直接降到 0.1 秒 —— 这差距,用户都能感觉到卡。
那 UUID 不能用,咱该用啥?别慌,5 种方案,咱一个个说,每个都给你拆明白。
第一个,数据库自增 ID—— 小系统的 “懒人首选”。建表时加个 AUTO_INCREMENT,插入数据不用管 ID,数据库自己给。比如用户表 id 从 1 开始,插一条涨一条,简单到离谱。
但你别以为这就没坑!分库分表的时候咋办?两个用户表,都从 1 开始自增,ID 肯定重复啊!这时候就得搞 “步长自增”:表 1 从 1 开始,步长 2(1、3、5…),表 2 从 2 开始,步长 2(2、4、6…),这样就不重复了。还有安全问题,自增 ID 是连续的,竞争对手看你订单 ID 从 10086 涨到 10186,就知道你一天卖 100 单 —— 想防这个,就加个随机数前缀,比如 10086 变成 23410086,既有序又难猜。
第二个,雪花算法—— 高并发的 “扛把子”。Twitter 搞出来的,用 64 位数字拆成三部分:41 位时间戳(撑 69 年)、10 位机器 ID(管 1024 台机器)、12 位序列号(一毫秒最多 4096 个 ID)。单机每秒能生成 400 多万个,完全扛得住大促。
但它最大的坑是时钟回拨。服务器时钟突然往后跳,比如回拨 100 毫秒,就会生成重复 ID。咋解决?简单,记录最后一次生成 ID 的时间戳,要是当前时间比最后一次小,就等 100 毫秒再生成 —— 等时钟追上了再干活。还有机器 ID,总不能手动给每台机器配吧?用 ZooKeeper,服务器启动时自动领一个唯一 ID,退出时还回去,省事儿。
第三个,美团 Leaf—— 不想造轮子就用它。Leaf 是在雪花算法基础上优化的,还支持 “号段模式”,特别适合中大型系统。
号段模式是啥?比如给订单业务一次拿 1000 个 ID(1-1000),放内存里慢慢用,用完了再去数据库拿下一段(1001-2000)。这样不用每次都访问数据库,性能高,而且数据库挂了,内存里还有号段能用,容灾性好。但要注意,服务器宕机的话,没用完的号段会丢,ID 会断 —— 不过大部分业务不在乎 ID 连续,这事儿问题不大。
它的雪花模式更省心,机器 ID 不用你配,ZooKeeper 自动分,时钟回拨也有处理:短回拨就等,长回拨直接报错,避免重复。美团官方说单机 QPS 能到 10 万 +,集群 100 万 +,够你用了。
第四个,UUID v7——UUID 的 “救赎版”。要是你不想改旧系统,又受不了 v4 的坑,就用 v7。它在 ID 里加了时间戳,前 10 位是时间戳(毫秒级),后 26 位是随机数,这样 ID 就有序了,索引友好,还兼容原来的 UUID 格式。
但它性能还是不如雪花 —— 毕竟是字符串,生成和存储都比数字慢。而且很多语言还不支持,比如 Java JDK17 得用第三方库(比如 java-uuid-generator),这点你得注意。
第五个,Redis 自增 ID—— 有 Redis 就用它。Redis 的 INCR 命令是原子性的,比如 order:id 这个键,每次调用 INCR 就加 1,返回的就是唯一 ID。简单粗暴,一行命令搞定。
但依赖 Redis 啊!Redis 挂了咋办?得搞主从 + 哨兵,保证高可用,还得开持久化(AOF+RDB),不然 Redis 重启后 ID 会丢。还有性能,虽然 Redis 单机能到 10 万 QPS,但比雪花的内存生成还是差,高并发下可能成瓶颈 —— 适合中低并发场景。
现在问题来了:你该咋选? 别瞎选,按场景对号入座:
- 小系统(比如公司 OA):直接用数据库自增,省事;
- 分布式高并发(电商大促):优先 Leaf 雪花模式或雪花算法;
- 想兼容旧 UUID 系统:选 UUID v7;
- 已经在用 Redis:Redis 自增 ID 走起。
最后再给你提几个必避的坑,别上线了才后悔:
- 算好 ID 长度:INT 类型自增最多 21 亿,用户量超了就用 BIGINT(922 亿亿);雪花算法 41 位时间戳撑 69 年,但起始时间要选好,别刚用 5 年就到期;
- 加监控:Leaf 号段快用完了要报警,Redis 挂了要通知,别等出问题才发现;
- 别暴露敏感信息:订单 ID 别用纯自增,加个随机数或 Base64 编码,不然竞争对手能算你订单量;
- 测碰撞:上线前用压力测试,每秒 10 万 ID 跑 24 小时,看有没有重复;
- 备预案:雪花时钟回拨了咋办?Redis 挂了咋切换?这些都要提前想好。
你看,ID 生成这事儿,不是随便选个 UUID 就完了。从避免碰撞到性能,从分布式兼容到容灾,每个环节都得想透。下次简历上再写 “精通 ID 生成”,先问问自己:这些坑,你真的都避开了吗?