01用户应该能挑自定义别名,还是系统生成的短码就够了?
用户可以提交长 URL,换回一个唯一的短链接——默认是系统生成的 base62 短码(字母 + 数字),自定义别名是可选的额外功能。
免费完整指南
为 1 亿日活跃用户设计一套可扩展的短网址服务。涵盖重定向、自定义别名、点击分析、缓存,以及一致性权衡。
面试节奏保持精简,让页面能把注意力花在真正的设计决策上。
不要只是陈述需求,要主动问出来。每张卡把设计约束和一句你可以在画架构前说出口的澄清问句配成一组。
01用户应该能挑自定义别名,还是系统生成的短码就够了?
用户可以提交长 URL,换回一个唯一的短链接——默认是系统生成的 base62 短码(字母 + 数字),自定义别名是可选的额外功能。
02链接永久存在,还是我们需要过期与清理?
用户可以设置可选的过期时间;过期的链接停止重定向,其短码稍后可以回收。
03重定向本身就是核心流程吗——点击时还必须发生别的事吗?
任何人打开短链接都会被重定向到原始 URL——这是遥遥领先的最频繁操作。每次点击也都必须被记录,而记录永远不能拖延重定向。
04营销活动需要批量创建吗——单次调用生成数千条链接?
支持批量创建:一场营销活动在单次请求中生成数千条链接。
05链接创建后可以更改目标到新的 URL 吗?
链接默认不可变;更改目标(retarget)是一个明确的可选功能——更改目标之后,访客必须落在新目的地,绝不能落在旧的。
06链接的所有权归谁——创建者能列出并停用自己的链接吗?
链接属于它的创建者:可列出、停用、回收——一条被停用的链接必须在数秒内停止重定向。
范围外分析仪表板——只有异步的点击事件送出在范围内 · 用户账号、身份验证,以及链接管理的 UI · 垃圾与恶意 URL 扫描
01我应该假设什么读写比——这是严重偏读取的吗?
读取密集:普通的一天约有 1 亿人点开短链接,对比约 100 万条新建链接——重定向对创建至少 100:1,营销密集的部署更接近 1000:1;值得问清楚这是哪一种。
02重定向对用户要感觉多快?
重定向 p95 低于 100 ms——短链接这一跳对点击的人必须感觉像不存在。
03全新的链接必须立刻在各处都能解析,还是短暂延迟可以接受?
可用性优先于一致性:重定向 99.99%;刚创建的链接可能要数秒才传播到缓存与副本(接受最终一致性)。
04短码空间与存储量应该规划到多少条链接?
规划约 10 亿条链接:7 字符的 base62 给出 62^7 ≈ 3.5 万亿个短码的余量;每行约 1 KB,总存储量接近 1 TB,可按 short_code 分片。
05两条链接有可能共用一个短码吗——有人能猜到私密短码吗?
短码必须全局唯一——永不碰撞。私密链接的短码不能被猜到:可预测的连号会让陌生人把私密 URL 一条条枚举出来。
真实面试探得比一份整齐清单深得多。这些范围问句,区分出真正拷问问题的人和只是背诵的人。
把每个估算都当成一种压力,用来合理化一个组件:缓存、队列、分片、副本、worker pool 或退路。
重定向读取 QPS
1 亿每日用户(上面那个读取密集的规模)× 每人每日约 1 次重定向 = 每日 1 亿次重定向100,000,000 次重定向 ÷ 一天 86,400 秒 ≈ 平均 1,160 QPS · 爆红突发 ×5-10 ≈ 峰值 5-10K QPS
缓存 + CDN 必须吸收峰值——数据库永远不服务重定向热路径。
创建写入 QPS
每日约 100 万条新链接1,000,000 ÷ 86,400 秒 ≈ 每秒 12 次写入
写入微不足道:不需要写入扩展——唯一的写入端风险是短码分配的竞争,而不是行的插入。
总存储量
10 亿条链接 × 每行约 1 KB(短码、长 URL、所有者、时间戳、TTL)10⁹ × 1 KB ≈ 1 TB
轻松放进分片存储;short_code 索引才是真正的工作集。
短码空间
7 字符的 base62 短码(每个位置 62 种字符)62⁷ ≈ 3.5 × 10¹² 个短码 vs 需要的 10⁹ 条链接 → 约 3,500 倍余量
短码永远用不完——真正的风险是可预测性(可枚举的短码),而不是耗尽。
缓存大小
假设约 20% 的链接服务约 80% 的读取(Pareto)20% × 1 TB ≈ 200 GB
一个中等规模的 Redis 集群就能办到;最热的爆红链接另外放在 CDN edge 上。
决策示例
数字只说一件事:读取比写入多约 100 比 1,峰值每秒 5–10K 次重定向,且必须在 100 ms 内回应——所以重定向是唯一值得优化的路径。
我会先从缓存回应重定向(Redis,最热的链接再加 CDN),让数据库永远不在热路径上。至于创建链接,一个小型 key 服务把一批预先做好的唯一短码交给每台 API 服务器——创建链接永远不会为了抢一个共享 counter 而卡住。点击靠往流里丢一个事件来计数;重定向永远不等分析。
我不会做的事:对长 URL 做哈希来生成短码、然后在两个 URL 碰撞时重试。在高负载下这些重试会堆积,创建链接就变成抽奖。发放预先做好的唯一短码,让碰撞变成不可能,而不只是不太可能。
如果精确的每次点击分析变成必备,我会从 301 切到 302 重定向:浏览器停止缓存这一跳,每次点击都到达我的服务器并被计数,而我接受那一点点额外延迟。
先看一张完整的图,再把每条路径各自画成一张图 —— 写入路径与读取路径承载不同流量,合理化不同的组件。
完整全貌
先从简单版开始:一张图,画出每个组件与关系。虚线 = 异步、不在热路径上。缓存 miss 时退回数据库并回填缓存。
路径 1
KGS 发放预先生成的唯一短码范围,所以创建永不碰撞、也永不为共享 counter 阻塞。
路径 2
缓存 miss 时退回数据库并回填缓存;点击事件走非路径的分析流。
在优化之前,先让契约可被检视:端点、实体、所有权、重试与状态。
POST/urls
请求{ long_url, custom_alias?, expires_at? }
响应200 { short_url, expires_at } · 409 别名已被占用 · 400 URL 无效
对 (owner, long_url) 幂等:重复提交返回既有的 short_url,而不是生成新短码。
GET/{short_code}
响应301/302 + Location: long_url · 404 未知短码 · 410 已过期
301 让浏览器缓存这次重定向(回源更少,但失去点击分析);302 让每次点击都经过你的服务器(保住分析,多一跳)。这个选择取决于分析需求——把它说出来。
核心实体
ShortLinkshort_code (PK) · long_url · owner_id · created_at · expires_at · is_active
按 short_code 分片,让一次重定向只打到一个分区;可选的倒排索引 long_url → short_code 可支持创建时去重。
Useruser_id (PK) · email · created_at
在面试最后三分之一挑一条路线。每条路线给你主题、它该回答的面试官问题,以及要避免的失败模式。
你如何保证两条链接永远不会拿到同一个短码——counter + base62,还是哈希?为什么选那个?
短码先预生成,别用哈希。一个独立服务提前造好唯一短码,再分给每台 API 服务器各自一整块去发。两台服务器不可能选到同一个短码,也没人需要先查数据库——哪怕营销活动高峰也一样。哈希只是让碰撞变罕见,而每次碰撞都得重试,偏偏发生在负载最高的时候。
对 URL 做哈希、寄望碰撞很罕见,却不解释唯一性保证。
走一次端到端的重定向:缓存在哪里命中、miss 时发生什么、数据库到底何时才被碰到?
短码先从 Redis 读。缓存命中几毫秒就返回,完全不碰数据库;miss 就读一次数据库,再回填缓存。最热的链接还放在 CDN edge 上。链接被停用或改了目标时,别等 TTL——立刻从 Redis 删掉,再 purge 掉 CDN 上的副本,让改动几秒内生效。
每次重定向都读数据库——热路径必须缓存优先。
你返回哪个重定向状态码?那个选择对浏览器缓存与你的点击数据有什么影响?
这里用 302,因为我们必须记下每一次点击。302 让浏览器每次都来问一遍服务器,所以每次点击都看得到——代价是多一跳。301 让浏览器把重定向永久缓存起来:更快,但重复点击就看不见了。既然记点击是硬需求,302 胜出。
随意挑一个——这个选择就是分析决策本身。
你如何在不增加延迟的前提下为 1 亿 DAU 记录点击?如果分析管线落后了会怎样?
重定向把一条点击事件丢进 Kafka 这样的持久化流里,然后立刻返回——计数是之后在旁边慢慢做的。就算这条管线落后了,数字会有一阵子不准,但重定向绝不会变慢。数字过时可以接受;重定向变慢不行。
在重定向内部同步写入分析。
单一链接突然吃掉全部流量的 10%——什么会先坏掉?缓存与 CDN 如何吸收它?
一条爆红链接会先压垮存着它的那个缓存分片,数据库还没反应过来。用 CDN edge 配一个短 TTL 来发它,几百万次点击在到我们这儿之前就被吸收掉了。API 服务器也可以在本地内存里留一份做后备。不管单条链接怎么飙,回源流量都稳如平地。
假设流量平均分布——热 key 才是真正的负载。
把 短链接服务 大声讲一遍,让 AI 为你的说明评分。