系统设计 Deep Dive:设计 URL 短链服务(TinyURL)
系统设计面试深度解析:基于真实候选人面经整理,涵盖SQL、Go、算法、系统设计等核心技术。还原系统设计题目、架构决策与面试官追问,附准备策略助你高效备战技术面试。
面试官真实提问
“请设计一个类似 TinyURL 的 URL 短链服务。用户输入一个长 URL,你返回一个短 URL。当用户访问短 URL 时,重定向到原始长 URL。”
这道题出现在 Google、Meta、Amazon 的系统设计面试中,通常是第一道系统设计题,用来考察候选人是否掌握基础的系统设计方法论。
需求澄清清单
功能需求
Must-have:
- 将长 URL 缩短为短 URL
- 短 URL 访问时 301/302 重定向到原始 URL
- 支持自定义短码(可选)
Nice-to-have:
- URL 过期设置
- 点击统计与分析
- QR 码生成
- URL 预览功能
规模估算
| 指标 | 估算值 |
|---|---|
| DAU | 1000 万 |
| URL 创建 | 每天 1 亿次(写) |
| URL 访问 | 每天 100 亿次(读) |
| 读写比 | 约 100:1(读远多于写) |
| 存储 | 每条 200 字节,1 亿条约 20GB/天,一年约 7.3TB |
非功能需求
- 可用性: 99.99%(URL 访问是核心功能)
- 延迟: P99 < 100ms(重定向要快)
- 一致性: 最终一致性可接受
第 1 步:高层设计
┌─────────┐
│ 客户端 │ Web / App
└────┬────┘
│
┌────▼────┐
│ LB │ Nginx / 负载均衡
└────┬────┘
│
┌─┴─┐
┌──▼──┐ ┌──▼──┐
│ API │ │ API │ Node A / Node B
│ A │ │ B │
└──┬──┘ └──┬──┘
└──┬──┘
┌──▼──────┐
│ Redis │ 缓存
│ Cluster │
└──┬──────┘
│
┌─┴─┐
┌───▼┐ ┌───▼──┐
│MySQL│ │MySQL │ Shard A / B
│ A │ │ B │
└─────┘ └──────┘
核心组件:
- 客户端:Web/App,发起创建短链和访问短链请求
- 负载均衡器:分发请求到 API 服务器
- API Server:处理业务逻辑,短码生成和重定向
- Redis Cluster:缓存热点短 URL,减少数据库压力
- MySQL(分片):持久化存储 URL 映射关系
第 2 步:核心组件设计
API 设计
# 创建短链
POST /api/v1/shorten
{
"long_url": "https://example.com/very/long/url/..."
}
Response:
{
"short_url": "https://tiny.url/abc123",
"short_code": "abc123"
}
# 重定向(301/302)
GET /abc123
→ 302 Found → Location: https://example.com/very/long/url/...
# 获取 URL 信息
GET /api/v1/info/abc123
{
"short_url": "https://tiny.url/abc123",
"long_url": "https://example.com/very/long/url/...",
"created_at": "2026-05-06T10:00:00Z",
"click_count": 1234
}
数据模型
CREATE TABLE urls (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
short_code VARCHAR(8) UNIQUE NOT NULL,
long_url TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NULL,
click_count INT DEFAULT 0,
user_id BIGINT NULL,
INDEX idx_short_code (short_code),
INDEX idx_created_at (created_at)
);
短码生成算法(核心考点)
这是这道题最重要的部分。面试官主要考察你对唯一性、可扩展性和安全性的理解。以下是三种主流方案:
方案 1:数据库自增 ID + Base62 编码
原理: 利用数据库自增 ID(Auto Increment),将其转换为 62 进制字符串(0-9, a-z, A-Z)。
ID=1→"1"ID=62→"z"ID=63→"10"
| 优点 | 缺点 |
|---|---|
| 简单高效:实现最容易,短码最短 | 可预测性(安全漏洞):攻击者可以通过遍历 ID 获取其他用户 URL |
| 无冲突:数据库保证 ID 唯一 | 单点瓶颈:并发写入压力大 |
| 有序性:短码包含时间顺序信息 | 暴露业务量:ID 直接反映系统 URL 总量 |
方案 2:哈希算法(MD5/SHA)
原理: 对原始长 URL 进行哈希计算(如 MD5),截取前 8 位作为短码。
| 优点 | 缺点 |
|---|---|
| 天然分布式:不需要数据库协调 | 冲突风险:必须查库校验,冲突则重试 |
| 不可预测:短码随机,无法遍历 | 短码较长:通常需要 8 位(Base62^8 ≈ 21 万亿) |
| 确定性:相同 URL 生成相同短码 | 无法统计:无法区分不同的分享事件 |
方案 3:分布式 ID(Snowflake)+ Base62
原理: 使用 Snowflake 算法生成唯一 ID(时间戳 + 机器 ID + 序列号),然后转为 Base62。
| 优点 | 缺点 |
|---|---|
| 高吞吐:单机每秒可生成数十万 ID | 依赖时钟:时钟回拨可能导致 ID 冲突 |
| 无冲突:算法保证全局唯一 | |
| 不可预测:包含随机因素,无法遍历 | |
| 趋势递增:写入数据库时索引局部性好 |
方案对比总结
| 特性 | 自增 ID + Base62 | 哈希算法 (MD5) | Snowflake + Base62 |
|---|---|---|---|
| 唯一性 | 保证 | 需处理冲突 | 保证 |
| 安全性 | 差 (可遍历) | 好 | 好 |
| 扩展性 | 差 (DB 瓶颈) | 好 | 极好 |
| 短码长度 | 最短 | 较长 | 中等 |
| 实现复杂度 | 低 | 低 | 中 |
面试推荐方案:Snowflake + Base62
话术: “考虑到系统需要高并发写入(扩展性)且不希望用户遍历 URL(安全性),我推荐使用 Snowflake 生成唯一 ID 并转为 Base62 的方案。如果规模较小,初期也可以使用 Redis INCR 模拟自增 ID。“
详细请求流程
创建短链流程:
- 客户端 POST
/api/v1/shorten,传入long_url - API Server 用 Snowflake 生成唯一 ID
- ID → Base62 编码 →
short_code - 写入 MySQL(short_code, long_url)
- 写入 Redis 缓存(short_code → long_url,TTL 24h)
- 返回
short_url给客户端
访问短链流程:
- 客户端 GET
/abc123 - API Server 查 Redis 缓存
- 缓存命中 → 302 重定向 + 异步更新点击数
- 缓存未命中 → 查 MySQL → 写入 Redis → 302 重定向
第 3 步:扩展性与优化
瓶颈分析
瓶颈 1:数据库写入压力
- 每天 1 亿次创建,QPS ≈ 1157
- 单台 MySQL 处理不了 → 分片(Sharding)
- 按
short_code哈希分片到多个数据库
瓶颈 2:热点 URL 读取
- 热门 URL(如营销链接)可能被数百万次访问
- 多级缓存策略:
- L1:API Server 本地缓存(Guava/Caffeine,TTL 1 分钟)
- L2:Redis Cluster(TTL 24 小时)
- L3:CDN 缓存 302 重定向响应
瓶颈 3:自增 ID 冲突
- 多服务器同时生成 ID 会冲突
- 解决: 用 Snowflake 算法,每台服务器分配不同
worker_id
容量估算
| 指标 | 计算 | 结果 |
|---|---|---|
| 存储 | 200 字节 × 1 亿/天 × 365 天 | 7.3TB |
| 数据库 | 每 shard 500GB | 约 15 个 shard |
| Redis | 缓存 10% 热点数据 | 约 200GB |
| 带宽 | 100 亿次/天 × 500 字节/次 | 115MB/s 平均 |
面试官常问 Trade-offs 与实战问答
Q1:你选择 301 还是 302 重定向?为什么?
候选人回答:
“我选择 302 临时重定向。因为 301 是永久重定向,浏览器会缓存这个映射,后续请求不会再经过我们的服务器,这样我们就无法统计点击数据了。虽然 302 每次都要请求服务器,但我们可以通过 CDN 缓存 302 响应 来优化性能,同时保留统计能力。”
面试官追问:
“如果 CDN 缓存了 302 响应,用户修改了长 URL 怎么办?”
候选人回答:
“设置合理的 TTL(比如 5 分钟),并在用户更新 URL 时主动清除 CDN 缓存。对于需要实时更新的场景,可以在 URL 中添加版本参数绕过 CDN 缓存。“
Q2:你为什么要用 MySQL 而不是 NoSQL?
候选人回答:
“URL 短链的数据模型非常简单,就是 key-value 映射。MySQL 的查询性能完全够用,而且支持复杂查询(比如按时间范围统计、用户维度分析)。如果未来写入量极大(每秒数十万),我会考虑迁移到 Cassandra 这样的宽表数据库。”
面试官追问:
“Cassandra 和 MySQL 在写入性能上有什么区别?”
候选人回答:
“Cassandra 使用 LSM-Tree 存储引擎,写入是追加操作,性能极高。MySQL 使用 B+ 树,写入需要维护索引,性能相对较低。但 Cassandra 的最终一致性模型对于 URL 短链来说是可以接受的。“
Q3:短码长度选多少位合适?
候选人回答:
“6 位 Base62 可以提供 568 亿 个唯一短码,对于大多数场景足够了。如果预期规模更大,我会选择 7 位(约 3500 亿)。同时会预留一些短码给自定义短码功能。”
面试官追问:
“如果用户想要自定义短码(比如 my-brand),怎么处理冲突?”
候选人回答:
“先检查自定义短码是否已被占用。如果冲突,提示用户选择其他短码。同时需要防止用户占用系统保留的短码(比如 admin、login 等)。“
Q4:Redis 缓存策略是怎样的?
候选人回答:
“我采用多级缓存策略。第一级是 API Server 的本地缓存(Guava/Caffeine),TTL 1 分钟,处理超热点 URL。第二级是 Redis Cluster,TTL 24 小时,处理大部分读取。第三级是 CDN 缓存 302 响应,进一步减少回源请求。”
面试官追问:
“Redis 宕机了怎么办?”
候选人回答:
“降级为直接查询 MySQL。虽然延迟会增加,但服务仍然可用。同时会触发告警,运维团队紧急修复 Redis。本地缓存仍然可以处理部分热点请求。“
Q5:怎么处理 DDoS 攻击?
候选人回答:
“多层防护策略。第一层在 CDN/WAF 层,过滤已知的恶意 IP 和异常流量模式。第二层在负载均衡器,设置 rate limiting。第三层在 API Server,对单个 IP 做限流。同时会监控流量异常,自动触发防护规则。”
面试官追问:
“如果攻击流量来自正常用户(比如病毒式传播)?”
候选人回答:
“这种情况下不能简单封禁。需要扩容(自动扩缩容),并优化缓存策略。同时监控业务指标,区分正常流量和恶意流量。“
进阶扩展方向
- 自定义短码: 用户输入
my-brand,需处理冲突 - URL 过期: 定时任务清理过期记录,Redis TTL 自动过期
- 统计分析: 独立 PV/UV、地域分布、设备类型、referrer
- 安全防护: 恶意 URL 检测、黑名单、病毒扫描
常见踩坑点
| # | 踩坑点 | 解决方案 |
|---|---|---|
| 1 | 短码冲突:哈希方案必须处理碰撞 | 查数据库 → 加随机后缀重试 |
| 2 | 热点 Key:大 V 发的链接可能占 50% 流量 | 本地缓存 + CDN 缓存 |
| 3 | URL 长度限制:长 URL 可能超过 2048 字符 | 输入验证 + 截断处理 |
| 4 | 恶意 URL:短链可能被用于钓鱼 | 安全审核 + 黑名单 |
| 5 | 统计准确性:异步更新点击数可能丢失 | 接受最终一致性 |
总结
URL 短链服务看似简单,但考察了系统设计的核心能力:
| 能力 | 考察点 |
|---|---|
| 容量估算 | 从业务场景推导系统规模 |
| 算法选择 | 短码生成的多种方案及权衡 |
| 缓存策略 | 多级缓存应对读多写少的场景 |
| 扩展性 | 分片、负载均衡应对高并发 |
面试提示: 不要一上来就画图。先花 3-5 分钟澄清需求,明确规模,再开始设计。面试官更看重你的思考过程,而不是最终答案。
推荐阅读
- 系统设计面试完全指南 — 掌握万能回答框架
- 设计速率限制器(Rate Limiter) — 另一道入门级经典题目
💡 需要面试辅导?
如果你对准备技术面试感到迷茫,或者想要个性化的面试指导和简历优化,欢迎联系 Interview Coach Pro 获取一对一辅导服务。
👉 联系我们 获取专属面试准备方案