系统设计 / 路由设计 · 稳定入口与可变目标 / 短链接为什么会存在 待审核 2 / 23

短链接为什么会存在

随手复制的一条链接常常是一两百个字符,后面拖着一长串给机器看的 utm_*ref 参数。它机器能用,人却几乎用不了:没法念给人手敲、塞不进有字数上限的短信、印成二维码后码点过密难以扫描、贴进聊天框还折成三行。

短链解决的就是这个长度与可用性问题:把任意长的 URL 换成一个极短、固定长度的代号,真正的长地址存在服务端一张表里,访问短码时再用 3xx 跳过去。它同时附带获得了 indirection 的全部好处(改址、统计、灰度),但首先解决的是长度这个最朴素的问题。

图 0-1 · 同一条长 URL 与它的短码在短信、二维码、口播、印刷四种场合的可用性。可改长 URL 观察各处的容纳情况。

短链的本质就是一张「短码到长 URL」的映射表加一次跳转。短码极短所以能念、能记、能印、能扫;真实目标藏在服务端所以随时能改、能在跳转那一下记统计、能按渠道灰度。

1 · 自增 id 与 base62

短码怎么生成才能又短又不撞?最经典的做法是每来一个新链接,数据库给它一个自增 id,再把这个十进制 id 用 base620-9a-zA-Z 共 62 个字符)编码。base62 的进制更大,同样的数用更少的字符就能写完,而且自增天然不重复,不像随机短码要查重、也不像 hash 截断会撞。

容量算一下就知道为什么够用:624147762^4 \approx 1477 万、6259.1662^5 \approx 9.16 亿、62656862^6 \approx 568 亿、6273.562^7 \approx 3.5 万亿。也就是说 5 位 base62 能编到 6251=916,132,83162^5 - 1 = 916{,}132{,}831,6 位到 6261=56,800,235,58362^6 - 1 = 56{,}800{,}235{,}583

图 1-1 · 自增 id 到 base62 短码的编码过程与各位宽的容量。可拖 id 或点预设,看短码位数在容量边界上如何进位。

2 · 自增 id 的可枚举风险

base62 只是换了进制,是可逆的双射,拿到一条短码解回 id 之后,相邻的 id±1id \pm 1 就是另一条短链。于是只要从 1 一路加上去,就能把整库的目标 URL 全爬下来。即便短码本身不可逆,单调递增也会泄露规模与增速——看到 id 到了 5000 万就知道发了多少,采样两次还能算增长,这是经典的德国坦克问题。goo.gl 与早期 bit.ly 都被枚举过,暴露出大量本以为不公开的网盘分享。要害只有一句:短码的难猜当不了访问控制,它只负责寻址。

图 2-1 · 从一条短码顺着 id 枚举出邻居的过程,右列对照一个把 id 打乱成同位宽不可预测码的置换。

警示 · 图 2-1 右列那个可逆置换仅作示意。真实生产里它必须是密码学强的(格式保留加密 FPE,或一个小 Feistel 网络),否则像图里的线性置换,被采几个样本就能反推出规律。

3 · 让短码既不撞又不可枚举

三种主流方案都在保住「短」的前提下消除「可猜」。

能接受查库防撞就选随机方案,最简单;想保留自增的内部好处就用加密,需要强保证时用 FPE 或 Feistel,不要只依赖 Hashids。
方案 怎么做 权衡
随机短码 不用 id,直接随机生成 6 至 8 位 base62 最简单;但要查重防撞,或靠空间够大让撞概率可忽略
加密自增 id 内部仍自增(不撞、好分库),对外只暴露 encrypt(id) 的结果 兼顾两者。Sqids 与 Hashids 只能阻止随手枚举、并不是加密,顺序可被还原
自增加随机校验位 短码是 base62(id) 再拼几位随机后缀 改动最小;但 id 段仍单调,规模与增速照样泄露,只挡住了「加减一猜邻居」

警示 · 凡是真正私密的目标(私人相册、未发布草稿、内部文档),不要拿短码的难猜当门禁。短码再随机也只是 security through obscurity,链接一旦泄露就全开。访问控制(登录、权限、过期、一次性 token)必须另做一层,短码只管寻址。这与分享入口那条 open redirect 原则是同一件事的两面:跳转本身不可信任输入。

4 · 长 URL 与短链的对照

前三行的长度与可用性是短链独有的存在理由;后两行的改址与统计是它从 indirection 附带获得的。
维度 直接发原始长 URL 发短链
字数受限渠道(短信 / 微博) 占满甚至超限,拆多条 / 吃光字数 ✗ 十几字符,正文随便写 ✓
二维码 version 高、码点密,印小后难以识别 ✗ version 低、稀疏,易扫描 ✓
口播 / 印刷 念不出来、折行、字号极小 ✗ 一行大字、能逐字念 ✓
发出后改目标 已印 / 已发的改不了 ✗ 改服务端映射表,全部跟随 ✓
来源统计 utm 自带但落地页各算各的 ✗ 跳转那一下统一记录 ✓
代价 无,但前几行的债都留着 多一跳、一张表,且依赖短链服务存活

警示 · 短链最大的代价是 link rot:真实地址被藏在某一家短链服务背后,这家一旦关停(Google 的 goo.gl 已下线,不少免费服务转付费墙后清库),所有印在海报、名片、书里的短链随即全部变成死链,且无法挽救——手里只有短码,原始 URL 在别人库里。重要的长期链接要么自建短链域名,要么直接用原始 URL。另外短链看不出真实目标,也常被钓鱼利用。

访问短码后跳过去那一下用的就是 3xx,短链多用 302 而非 301,因为目标常变且要统计点击。把短码表做成一个真实的 /s/:code 服务(命中、统计、查表、跳目标)是分享入口的题目。