百亿级短 URL 生成器:如何保证“短链不冲突”
我们采用“全局唯一 ID → Base62 编码”的短码生成方式:生成阶段先通过号段/雪花算法获得唯一 64-bit ID,再将其编码为 Base62 短码。由于 ID 全局唯一,短码天然不冲突;同时在数据库层对短码建立 UNIQUE 索引作为最后防线,确保在异常、迁移或人为操作下仍不会出现重复映射。
1. 问题本质:冲突来自哪里?
短 URL 一般长这样:
https://s.io/Ab3xK9(6~8 位 Base62)- 其中
Ab3xK9叫 短码(short code)
冲突就是:不同长链接/不同记录生成了相同 short code。冲突一旦发生会导致:
- 覆盖旧映射(灾难)
- 或插入失败(吞吐下降、重试风暴)
所以核心是:在并发生成时,保证 short code 的唯一性(尤其跨机房、多实例)。
2. 方案总览(按推荐优先级)
A. ✅ 强烈推荐:全局唯一 ID(单调递增)→ Base62 编码(零冲突/高吞吐)
思路:先拿到一个全局唯一的 id(比如 64-bit),然后把 id 用 Base62 编码成短码。
id唯一 ⇒short code必然唯一- 无需“查重重试”,吞吐稳定
- 可按业务拆分号段/分库分表
常用 ID 来源:
- 数据库自增(单库可行,跨库要号段)
- 号段服务(Segment/Hi-Lo)
- Snowflake/Leaf/UID Generator(时间戳+机房+机器+序列)
关键点:短码长度可控:
Base62 编码长度约为log_62(id)
- 6 位 Base62 ≈ 56B(62^6 ≈ 5.68e10,约 568 亿)
- 7 位 Base62 ≈ 3.5 万亿
所以 6 位已能支撑“百亿级”总量(但要考虑业务预留、不同域/租户等)。
B. ✅ 工程上也常用:随机短码 + 数据库唯一索引 + 冲突重试(可行但要算概率)
思路:随机生成 6~8 位 Base62,写库时对 short_code 建 UNIQUE 索引;冲突则重试生成新的。
优点:
- 不依赖全局序列服务,结构简单
- 多机扩展天然并行
缺点:
- 百亿量级下冲突概率会显著升高(生日悖论),重试会带来抖动
- 高峰期可能出现“重试风暴”,尾延迟变差
适用:量级不大、或短码长度足够长(比如 8~10 位),且能接受少量重试。
经验:如果坚持随机法,尽量:
- 位数更长(8+)
- 生成时加“租户/域/业务前缀”分散空间
- 重试次数上限 + 降级策略(例如退到序列法)
C. ✅ 兼顾可读性/可控长度:哈希(长链接)→ 截断 + 兜底扩展(需处理碰撞)
思路:对原始长链接(或规范化后的 URL)做 hash(如 SHA256),取前 N 位 Base62 作为短码。
优点:
- 同一个长链接可稳定生成同一个短码(幂等)
- 无需额外查询(理论上)
缺点:
- hash 截断一定存在碰撞概率,必须有碰撞处理机制
- 想做到“绝对不冲突”,最终仍要依赖唯一约束或额外字段(salt/扩展位)
适用:需要“同一 URL 幂等短码”的业务场景(比如营销物料)。
3. 推荐架构:ID→Base62(主路)+ 唯一索引(最后防线)
3.1 数据模型(核心两列)
id:全局唯一主键(BIGINT/64-bit)short_code:Base62(id) 或 Base62(可逆映射)
并对 short_code 建 UNIQUE(强烈建议,即使理论上不冲突,也当作防线):
- 防止人为导入、bug、回滚、并行写入异常等导致的脏数据
- 防止未来更换算法或迁移时出现重复
3.2 生成流程(高并发稳定)
- 获取全局唯一
id(号段/Snowflake) short_code = Base62(id)- 插入映射记录(依赖
idPK +short_codeUNIQUE) - 返回短链
这条路径几乎 无重试、无抖动,能支撑非常高的 QPS。
4. 关键细节:百亿级要注意的“坑”
4.1 短码长度规划:别刚好卡上限
- 6 位 Base62 上限约 568 亿
- 百亿级业务通常会有:
- 多域名/多租户
- 预留、废弃短码
- 灰度/迁移双写
- 黑名单/敏感词过滤导致的跳号 建议:
- 直接用 7 位 做长周期规划(空间暴涨)
- 或 6 位起步,设计可平滑扩容到 7 位(见下)
4.2 平滑扩容(从 6 位到 7 位)
常见做法:
- 不强制固定长度:Base62(id) 自然增长,id 变大时长度自动+1
- 解析时不依赖长度,只按字符串查表
- 或按版本前缀:
v1_XXXXXX/v2_XXXXXXX(更利于迁移)
4.3 分库分表与路由
百亿数据量会非常大,通常需要分片:
- 以
id或short_code做 hash 分片 - 查短链(short_code → long_url)要走稳定路由,避免跨库扫
建议:
short_code用于查询入口(高频)id用于写入顺序、冷热分层、归档策略
4.4 缓存与热点
短链跳转是读多写少:
- Redis 缓存
short_code -> long_url - 热点短链会非常热:需要缓存穿透/击穿保护
- 本地缓存(LRU)+ Redis
- 负缓存(不存在也缓存短 TTL)
- 单飞/互斥锁防击穿
4.5 删除/过期策略
短链是否允许过期、是否复用短码?
- 强烈不建议复用短码(复用会造成历史链接被错误指向)
- 过期建议“逻辑过期”:
status=expired,跳转到提示页- 仍保留映射(或归档到冷存储)
5. 三种方案对比)
| 方案 | 冲突风险 | 吞吐稳定性 | 实现复杂度 | 是否适合百亿 |
|---|---|---|---|---|
| ID(全局唯一) → Base62 | 极低(理论 0) | 极高 | 中 | ✅✅✅ |
| 随机短码 + UNIQUE + 重试 | 中(随规模升高) | 中(尾延迟抖动) | 低 | ⚠️(需更长位数) |
| Hash 截断 + 碰撞处理 | 低~中 | 中 | 中 | ⚠️(要兜底) |
6. 最推荐的落地结论
- 主路:全局唯一 ID(号段/Snowflake)→ Base62 编码
- 数据库层:给 short_code 加 UNIQUE,当最后防线
- 长度:优先 7 位规划(6 位也行,但要预留扩容)
- 读性能:Redis 缓存 + 防穿透/击穿
- 不复用短码,过期用状态控制