分布式ID方案选型与实战:雪花算法、号段、UUID 怎么选?

📅 发布时间:2026/7/6 5:17:41 👁️ 浏览次数:
分布式ID方案选型与实战:雪花算法、号段、UUID 怎么选?
前言分库分表、多节点写入时单机自增ID不够用了。订单号、用户ID、日志追踪ID都要全局唯一、尽量有序、高性能还要考虑数据库压力和安全。市面上方案很多UUID、雪花算法、号段模式、Redis 自增……到底选哪个本文从场景出发对比常见分布式ID方案的特点和适用场景并给出 Java/Go 下的实战示例方便你在项目里直接选型、落地。1. 先搞清楚分布式ID要满足什么要求说明全局唯一多节点、多表不能重复趋势递增利于 MySQL 主键索引、分库分表路由高性能本地生成、少依赖外部服务高可用不依赖单点故障影响小信息安全尽量不暴露业务量、不连续可猜不同业务侧重点不同订单号要可读、防篡改用户ID要短、可暴露链路追踪要全局唯一、易生成。2. 方案一UUID2.1 特点优点本地生成、无网络、实现简单、绝对不重复。缺点无序随机 UUID、36 位字符串、做 MySQL 主键会导致页分裂、索引效率差无业务含义。2.2 适用场景临时凭证、一次性 token、日志追踪 ID。不做主键、不按范围查询的场景。2.3 示例// Javaimportjava.util.UUID;StringidUUID.randomUUID().toString();// 550e8400-e29b-41d4-a716-446655440000// Goimportgithub.com/google/uuidid:uuid.New().String()# 命令行uuidgen2.4 小结适合「只要唯一、不关心顺序」的场景不适合做 MySQL 主键或订单号。3. 方案二数据库自增单库/多库步长3.1 单库自增单库时用AUTO_INCREMENT即可简单可靠。分库分表后各自自增会重复不能直接当全局ID。3.2 多库步长自增每台库设置不同起点和步长例如 2 台库库1auto_increment_increment2auto_increment_offset1→ 1, 3, 5, 7…库2auto_increment_increment2auto_increment_offset2→ 2, 4, 6, 8…优点实现简单、趋势递增。缺点扩容要调步长、迁移麻烦强依赖数据库写压力大。适合节点数固定、写入量不大的场景现在用得越来越少。4. 方案三号段模式Segment4.1 原理由中心服务或数据库表一次分配一段 ID例如 11000应用在内存里自增使用用完了再取下一段。[DB/服务] 分配 1~1000 → 应用A 本地 1,2,3...1000 分配 1001~2000 → 应用B 本地 1001,1002...4.2 优点数据库压力小一次取一批。ID 连续、趋势递增适合做主键、分库分表。可做双 buffer 预取几乎无阻塞。4.3 缺点依赖中心服务或 DB中心挂了要等恢复或提前多取几段。会有「段内连续、段间可能浪费」的步长一般可接受。4.4 数据库表设计示例CREATETABLEsegment_id(biz_tagVARCHAR(32)PRIMARYKEYCOMMENT业务类型order/user,max_idBIGINTNOTNULLDEFAULT0COMMENT当前最大ID,stepINTNOTNULLDEFAULT1000COMMENT号段长度,update_timeDATETIMENOTNULL);-- 取号段原子更新UPDATEsegment_idSETmax_idmax_idstep,update_timeNOW()WHEREbiz_tagorder;-- 然后 SELECT max_id, step 得到 [max_id - step 1, max_id]4.5 Java 取号段示例简化// 伪代码取号段publicsynchronizedlongnextId(StringbizTag){if(currentend){// 从 DB 拉取新号段SegmentsegsegmentDao.getNextSegment(bizTag);currentseg.getMaxId()-seg.getStep();endseg.getMaxId();}returncurrent;}4.6 小结适合中等 QPS、希望少打 DB、且能接受依赖中心的业务很多大厂内部 ID 服务就是这样做的。5. 方案四雪花算法Snowflake5.1 结构64 bit1 bit符号位固定 0。41 bit毫秒级时间戳可用约 69 年。10 bit机器/节点 ID最多 1024 个节点。12 bit同一毫秒内序列每毫秒最多 4096 个 ID。同一毫秒内超过 4096 可等待下一毫秒或扩展序列位。5.2 优点本地生成、无网络、高性能。趋势递增对 MySQL 主键和 B 树友好。可包含时间信息便于排查和粗略排序。5.3 缺点强依赖时钟时钟回拨会导致重复或异常需要做时钟回拨处理等待/报错/换节点。机器 ID 要分配好否则多节点可能重复。5.4 Java 示例简化版publicclassSnowflakeIdGenerator{privatefinallongworkerId;privatefinallongepoch1609459200000L;// 2021-01-01 00:00:00privatelonglastTimestamp-1L;privatelongsequence0L;privatestaticfinallongSEQUENCE_BITS12L;privatestaticfinallongMAX_SEQUENCE~(-1LSEQUENCE_BITS);// 4095publicsynchronizedlongnextId(){longnowSystem.currentTimeMillis();if(nowlastTimestamp){thrownewIllegalStateException(时钟回拨拒绝生成ID);}if(nowlastTimestamp){sequence(sequence1)MAX_SEQUENCE;if(sequence0){nowtilNextMillis(lastTimestamp);}}else{sequence0L;}lastTimestampnow;return((now-epoch)22)|(workerId12)|sequence;}privatelongtilNextMillis(longlast){longnowSystem.currentTimeMillis();while(nowlast)nowSystem.currentTimeMillis();returnnow;}}5.5 Go 示例第三方库importgithub.com/bwmarrin/snowflakenode,_:snowflake.NewNode(1)// 节点ID 1id:node.Generate()// 返回 int645.6 小结适合高 QPS、多节点、能保证时钟可靠的场景是当前最常用的分布式ID方案之一。生产务必处理时钟回拨NTP、虚拟机挂起等。6. 方案五Redis INCR6.1 用法INCR id:order# 返回 1, 2, 3...可加前缀做业务隔离id:order、id:user。6.2 优点实现简单、性能高。可设置过期或按日期 key方便按天清零等策略。6.3 缺点依赖 Redis 可用性持久化不当可能丢一段AOF 可缓解。纯自增无时间信息要做成趋势递增需要 key 设计如带日期。适合对绝对连续要求不高、已有 Redis的辅助ID如活动计数、短链ID。7. 方案对比与选型建议方案唯一性有序性性能依赖适用场景UUID是否高无临时ID、追踪ID、不做主键库步长是是中DB节点数固定、小规模号段是是高中心/DB中高QPS、可接受中心依赖雪花是趋势递增高时钟节点ID高QPS、多节点、主键/订单Redis是是高Redis辅助ID、计数、短链简单选型订单号、用户ID、主键优先雪花或号段不能接受时钟风险选号段。临时 token、链路 IDUUID即可。已有 Redis、非主键可用Redis INCR做补充。8. 实战注意点8.1 订单号可读性雪花是数字可直接用若要带日期、业务前缀可在前面拼字符串例如ORD202502141234567890日期 雪花后几位或完整雪花转码。8.2 号段与雪花混用主键、分库分表路由用雪花或号段。对外展示订单号可在雪花前加前缀、或单独用号段/Redis 生成短号。8.3 安全与防爬不要用连续自增对外暴露雪花或号段对外可做一次编码如 Base62、洗牌算法。敏感接口加签名、限流不单靠 ID 防刷。9. 总结UUID无顺序要求、不做主键时用。号段少打 DB、趋势递增、可接受中心服务时用。雪花高 QPS、多节点、主键/订单号首选注意时钟回拨与节点ID分配。Redis辅助ID、计数、短链等补充方案。按业务选一种为主、其他补充就能覆盖绝大多数后端分布式ID需求。