设计 URL Shortener
发布: (2026年3月1日 GMT+8 12:44)
5 分钟阅读
原文: Dev.to
Source: Dev.to
要求
核心
- 用户可以将长 URL 创建为短 URL。
- 访问短 URL 会重定向到原始 URL。
可选
- 自定义别名(例如
short.ly/myname) - 链接的过期时间
- 分析(点击次数、地理位置、设备信息)
- 高可用性(服务几乎不宕机)
- 低延迟重定向(重定向应即时完成)
- 大规模(数百万条 URL,数十亿次重定向)
- 持久存储(即使服务器崩溃也不会丢失数据)
假设与负载估计
- 10 M 每月新增 URL
- 1 B 每月重定向
- Read‑to‑write ratio: ~100:1
含义
- 系统以读取为主。
- 缓存至关重要。
- 数据库必须水平扩展。
- 写入可控;读取可能激增。
高层架构
Client ──► Load Balancer ──► URL Generation Service ──► Key‑Value Store
│ │
▼ ▼
Redirection Service ◄── Cache (Redis) ◄───
写入路径(创建短链接)
- POST /shorten 请求到达负载均衡器。
- URL 生成服务创建唯一的短码。
- 映射(
short_code → long_url)存储在键值数据库中。 - 短链接返回给客户端。
读取路径(重定向)
- GET /{short_code} 请求命中负载均衡器。
- 重定向服务检查缓存(例如 Redis)。
- 缓存命中: 立即返回 HTTP 301/302 重定向。
- 缓存未命中: 从数据库获取映射,填充缓存,然后进行重定向。
API 规范
创建短链接
POST /shorten
Content-Type: application/json
{
"long_url": "https://example.com/very/long/url",
"custom_alias": "optional",
"expiry": "optional timestamp"
}
响应
{
"short_url": "https://short.ly/abc123"
}
重定向
GET /{short_code}
响应 – HTTP 301 或 302 重定向到原始 URL。
ID 生成策略
自动递增 ID + Base62
- 递增一个数值 ID。
- 将 ID 转换为 Base62(
a‑zA‑Z0‑9)。 - 保证唯一性,确定性,且易于扩展。
基于哈希
- 对长 URL 进行哈希并取前 6–8 个字符。
- 问题: 可能出现冲突 → 需要冲突解决。
复合(时间戳 + 机器 ID + 序列)
- 在多个服务器并发生成 ID 时很有用。
- 提供高可扩展性和唯一性。
分片
- 分片键:
hash(short_code) % N - 优势: 均匀分布,防止热点,查询简单。
数据库复制
- 主节点: 处理写入(新的短链接)。
- 只读副本: 提供重定向服务。
优势
- 读取占据大部分流量 → 可以独立扩展读取。
- 提高可用性;如果主节点故障,可提升副本为主节点。
缓存层
- 缓存键:
short_code → long_url. - 常见缓存: Redis 或内存存储。
缓存工作流
- 检查 Redis。
- 命中 → 立即重定向。
- 未命中 → 查询数据库,更新缓存,然后重定向。
缓存调优
- 条目 TTL(可选)。
- LRU 驱逐策略。
- 为热门 URL 预热缓存。
分析与点击计数
同步更新点击计数会减慢重定向速度。
更佳方案
- 将点击事件发送到消息队列(Kafka、RabbitMQ 等)。
- 后台工作者消费事件并异步更新分析数据。
优势
- 保持重定向路径快速。
- 将分析与用户体验解耦。
- 支持独立扩展。
边缘情况与权衡
- 自定义别名冲突: 拒绝或提示用户选择其他别名。
- 短码冲突: 使用确定性生成(自动递增)或通过重试解决冲突。
- 恶意/垃圾 URL: 集成 URL 安全检查(例如 Google Safe Browsing)。
- 链接过期: 返回 410 Gone 或自定义错误页面。
- 301 与 302: 大多数服务倾向于使用 302,以便以后可以更改目标地址。
摘要
URL 缩短服务是典型的读密集型系统。通过激进的缓存、只读副本以及异步分析来优化重定向路径,对于实现低延迟和高可扩展性至关重要。设计决策——例如 ID 生成、分片和缓存策略——必须在唯一性、性能和运维简易性之间取得平衡。