Skip to content

短链接服务 架构模板

代表产品:Bitly、TinyURL、t.co(Twitter)、各类二维码 / 分享短链 一句话定位:把一条长 URL 映射成一个极短的 key,点击短链时以最快速度重定向回原始地址。


1. 一句话定位

短链接服务 = 一张巨大的「短码 → 长链接」映射表 + 一条被优化到极致的重定向快路

它看起来简单到像一行哈希表,但恰恰是**「读多写少、极致可用、全局唯一 ID」的教科书**。真相是:99.9% 的流量都是「拿短码查长链,然后 302 跳走」这一个动作——这条路必须快到几十毫秒、且几乎不能挂(链接一旦印在海报、二维码、短信里,挂了就是大面积事故)。

2. 业务本质:它在解决什么问题

长链接又长又丑:短信有字数限制、二维码越长越密难扫、社交分享要好看可点。短链解决的是「把任意长的地址压缩成一个干净、可分发、可追踪的入口」。

附带的真正价值是点击分析:谁、什么时候、在哪、用什么设备点了这条链接。

钱从哪来:企业版的点击分析与营销报表、自定义品牌域名、批量生成 API、A/B 投放追踪。短链的本体几乎不赚钱,赚钱的是它背后那层「可度量的分发」。

3. 核心需求与约束

功能性需求:

  • [ ] 长链接 → 生成短码
  • [ ] 短码 → 重定向回长链接(核心中的核心)
  • [ ] 自定义别名(/spring-sale)、过期时间
  • [ ] 点击统计(次数、来源、地域、设备)

非功能性需求 / 质量属性(这才是架构主战场):

质量属性目标为什么对这类系统重要
重定向延迟< 50ms用户点了要立刻跳,慢一点就像「这链接坏了」
可用性99.99%+链接已经发出去、印出去了,服务挂 = 所有人点了都打不开
读写比常 100:1 甚至 1000:1创建一次,被点千万次。读写形态严重不对称
短码长度尽量短越短越好用,但越短可用空间越小,是个取舍

关键约束(不可逾越的边界):

  • 🔴 链接不可变:短码一旦生成并分发,就印在物料上,永远改不了。
  • 🔴 短码空间有限:可用短码数 = 字符集大小 ^ 长度。6 位 Base62 ≈ 568 亿,看着多,热门服务几年就逼近。
  • 🔴 读写极度不对称:架构必须围绕「读」来设计,「写」可以慢。

4. 架构全景图

   创建短链(少量)                          点击短链(海量,99.9% 流量)
        │                                          │
        ▼                                          ▼
┌────────────────┐                      ┌────────────────────────────┐
│   写服务         │                      │   读服务 / 重定向(快路)       │
│ • 校验长链      │                      │  ┌──────────────────────┐  │
│ • 申请唯一 ID    │◀── 发号器 ──┐        │  │ 1. 查 KV 缓存(命中即返)│  │
│ • ID → Base62   │   (号段/雪花) │        │  └──────────┬───────────┘  │
│ • 落库          │             │        │             │ 未命中        │
└───────┬────────┘             │        │             ▼              │
        │                      │        │  ┌──────────────────────┐  │
        ▼                      │        │  │ 2. 查主存储,回填缓存   │  │
┌────────────────┐            │        │  └──────────┬───────────┘  │
│  主存储(映射表) │◀───────────┘        │             ▼              │
│ short_key→long  │◀───────────────────│  3. 返回 301/302 重定向     │
└────────────────┘                      └─────────────┬──────────────┘
                                                       │ 异步上报

                                          ┌──────────────────────┐
                                          │ 点击事件队列 → 分析存储 │ (绝不拖慢重定向)
                                          └──────────────────────┘

灵魂部件是中间那条重定向快路:它要承受全部读流量,所以「缓存命中率」几乎等于这个系统的命运。其余一切都是为它服务。

5. 组件职责

  • 写服务:校验目标 URL、申请全局唯一 ID、把 ID 编码成短码、落库。为什么需要:创建是低频但要保证短码全局不重复。
  • 发号器 / ID 生成:产出全局唯一、不冲突的 ID。为什么需要:短码唯一性的根基;它一旦成为单点或瓶颈,整个写入就卡死(见决策 1)。
  • 读服务 / 重定向:查缓存 → 查库 → 返回重定向。为什么需要:这是系统 99.9% 的工作,必须独立、极简、可大量水平扩展。
  • KV 缓存:把热门短码的映射放在内存级缓存里。为什么需要:热点链接极热(一条爆款可能占全站一半点击),缓存命中率直接决定延迟和成本。
  • 主存储(映射表):short_key → long_url 持久化的真相源。
  • 点击事件管道(异步):把每次点击作为事件丢进队列,后台慢慢聚合。为什么需要:统计绝不能挡在重定向的关键路径上(见决策 3)。

6. 关键数据流

场景一:创建一条短链(低频写)

1. 用户提交长 URL ──▶ 写服务:校验合法性 / 黑名单
2. ──▶ 发号器:拿到一个全局唯一 ID(如 10000001)
3. 写服务:把 ID 用 Base62 编码 → "aUKYr";落库 {aUKYr → https://...}
4. 返回 https://sho.rt/aUKYr

场景二:点击短链并重定向(海量读,系统的命脉)

1. 用户点 https://sho.rt/aUKYr ──▶ 读服务
2. 查 KV 缓存:
      命中 ──▶ 直接拿到长链(99% 走这里)
      未命中 ──▶ 查主存储,拿到后回填缓存
3. 返回 HTTP 302 + Location: 原始长链  ── 浏览器自动跳转
4. (旁路)把这次点击作为事件异步丢进队列,主流程立刻结束 ◀── 不等统计写完

注意第 4 步:统计是「旁路」,不是「主路」。 重定向在第 3 步就结束了。这是「把非关键路径从关键路径上剥离」的典型。

7. 数据模型与存储选择

核心实体极简:映射(short_key, long_url, 创建者, 过期时间);点击事件(short_key, 时间, 来源, 地域, 设备)

数据存储类型为什么
短码 → 长链映射KV / 关系型(按 short_key 分片)主键精确查找、海量、无复杂关联
热点映射内存级 KV 缓存读路径的命脉,命中率决定一切
点击事件时序 / 列存海量追加、按时间和维度聚合,做报表

教学点:这是主键点查场景(永远是「拿一个 key 取一个 value」),没有 JOIN、没有范围扫描——所以 KV 形态的存储天生最合适,关系型也行但要把它当 KV 用。

8. 关键架构决策与权衡 ⭐

决策 1:短码怎么生成?

  • 自增 ID → Base62:短、无冲突、保证唯一。但短码连续可枚举(别人能顺着遍历你全站链接、甚至估出你的业务量)。
  • 随机生成:不可枚举、安全。但要查重(可能撞码),空间用满后撞码率升高。
  • 哈希长链取前几位:相同长链得相同短码(天然去重),但哈希冲突要处理。
  • 取向:多数选「自增 ID + 打乱/加盐再 Base62」——既拿到唯一性,又避免明文连续可枚举。代价是发号器要可靠。

决策 2:重定向用 301 还是 302?(经典取舍)⭐

  • 301 永久重定向:浏览器和 CDN 会缓存,后续点击根本不到你服务器——极快、极省。但你从此收不到点击统计了(请求被缓存拦截)。而且短码若想换目标就失效。
  • 302 临时重定向:每次都回到你服务器,点击统计完整,可灵活改目标。但服务器要扛全部流量。
  • 取向:要分析数据就用 302(这是商业模式所在),代价是自己扛流量、靠缓存优化。这一对取舍精准体现了「可缓存性 vs 可观测性」的永恒矛盾。

决策 3:点击统计同步还是异步?

  • 同步:重定向时顺便把统计写库。简单,但把慢的写操作塞进了最该快的路径,统计存储一抖动,重定向就变慢。
  • 异步:点击只往队列里扔一个事件,主流程立刻返回重定向。
  • 取向:必须异步。重定向是关键路径,统计是旁路,两者绝不能耦死。

9. 规模化与瓶颈

  • 第一个瓶颈:读流量打爆。 → 破解:多级缓存(本地 + 分布式 KV)、把热门短链推到 CDN / 边缘节点直接重定向、读服务无状态水平扩。
  • 第二个瓶颈:中心发号器成为写入单点。 → 破解:号段模式(一次批发一段 ID 给各节点本地用)或雪花算法(各节点自己生成不冲突 ID)。详见 电商平台模板 里的发号思路。
  • 第三个瓶颈:点击事件洪峰(爆款链接被疯转)。→ 破解:消息队列削峰(见 04 · 消息队列/异步),后台批量聚合。
  • 第四个瓶颈:映射表太大。 → 破解:按 short_key 哈希分片。因为是纯点查,分片极其干净(没有跨片查询)。

10. 安全与合规要点

  • 🔴 开放重定向 = 天然钓鱼工具:短链隐藏了真实目标,攻击者爱用它把人骗到恶意站点。架构上必须校验 / 扫描目标 URL、维护恶意域名黑名单、对可疑链接加风险提示页
  • 枚举遍历:连续短码会被爬虫顺序抓取,泄露全部链接和业务量。→ 短码加盐打乱或随机化。
  • 滥用刷量:有人刷点击伪造数据 → 限流、去重、风控。
  • 隐私:点击数据含 IP / 地域 / 设备,属个人信息,要合规处理与脱敏。

11. 常见误区 / 反模式

  • 同步写点击统计,拖慢重定向 → ✅ 统计走异步队列,重定向是神圣的快路。
  • 用明文连续自增做短码 → ✅ 加盐打乱,避免可枚举、可猜量。
  • 不做缓存,每次点击都打主存储 → ✅ 多级缓存,命中率是命根子。
  • 图省事用 301,结果丢了所有分析数据 → ✅ 想清楚要不要统计,再选 301/302。
  • 不校验目标 URL,沦为钓鱼帮凶 → ✅ 黑名单 + 扫描 + 风险拦截。
  • 把它当复杂系统过度设计 → ✅ 它的难点只在「读路径的快和稳」,别的都该极简。

12. 演进路线:MVP → 成长期 → 成熟期

阶段规模量级架构长什么样此时该操心什么
MVP日点击 < 10 万单体 + 一个数据库 + 自增 ID 转 Base62;同步统计也无妨先把「生成 + 重定向」跑通,别想太多
成长期百万~千万读写服务拆开;加 KV 缓存;点击统计异步化;发号器换号段模式找读路径瓶颈、把缓存命中率做上去
成熟期亿级 + 全球多区域多活、边缘 / CDN 重定向、映射表分片、独立的实时分析管道全球延迟、容灾、防滥用、分析体系

13. 可复用要点

  • 💡 读多写少的系统,要把读路径优化到极致,并和写路径彻底解耦。 这是缓存、读写分离、CQRS 一切的出发点。
  • 💡 301 vs 302 是「可缓存性 vs 可观测性」的微缩模型。 任何「让中间层帮你缓存」的设计,几乎都要拿「我还看不看得见这次访问」来换。
  • 💡 全局唯一 ID 是分布式系统的基本功:中心发号、号段、雪花,各有取舍,值得吃透。
  • 💡 把非关键路径(统计)从关键路径(重定向)上剥离,是降低延迟、提升稳定性的通用手法。

🎯 随堂检验

🤔短链接服务的架构重心应该放在哪条路径?
  • A写(生成短码)路径
  • B读(重定向)路径 + 缓存
  • C两者均等对待

参考原型与延伸阅读

本模板基于以下真实开源项目整理(短链服务 + 分布式发号)。

🔧 开源原型(可直接读代码):

  • YOURLS/YOURLS — 经典自托管短链,短码生成 / 自定义关键词 / 点击统计 / 重定向。
  • shlinkio/shlink — 自托管短链服务,读多写少的重定向 + REST API + 多域名短链。
  • thedevs-network/kutt — 现代化短链(Node.js),链接管理 / 统计 / 自定义域名。
  • twitter-archive/snowflake — 分布式时序唯一 ID 发号(时间戳+机器ID+序列号),短码发号的经典方案。

📌 一句话记住短链接服务:它不是「一行哈希表」,而是「一条要扛住全世界点击、且永远不能挂的重定向快路」——所有设计都在回答『怎么让查 key 这件事又快又稳又省』。