设计 TinyURL:短链接生成与重定向
理解短链接系统
短链接服务(如 TinyURL、Bitly)的核心功能是将一个冗长的 URL 转换为简短的别名,并在用户访问该短链接时,将其重定向到原始长 URL。这背后涉及短码生成策略、存储方案、高并发重定向以及系统可扩展性等关键设计。
核心需求分析
一个成熟的短链接系统需要满足以下功能和非功能需求:
- 功能需求
- 用户提交长 URL,系统返回唯一短链接。
- 用户访问短链接时,系统执行 301/302 重定向至原始 URL。
- 可选:自定义短码、有效期设置、访问统计。
- 非功能需求
- 高可用:重定向服务不能中断。
- 低延迟:重定向必须在毫秒级完成。
- 可扩展:支持数百亿链接的存储和每秒百万级查询。
- 安全性:防止短链接被猜测枚举、恶意利用。
短码生成算法
短码是短链接系统的灵魂。它的长度、唯一性和生成效率直接影响整个系统。常用的生成策略有以下几种。
1. 哈希函数法
对长 URL 进行哈希计算(如 MD5、SHA-256),然后取前 6-8 位作为短码。
- 示例:
shortCode = base62(md5(longUrl))[:7] - 优点:实现简单,不需要全局计数器,去中心化。
- 缺点:存在哈希冲突(不同长 URL 可能生成相同短码),需要额外处理冲突(如重试、加盐)。
- 规避冲突:每次生成后检查数据库是否已存在,若冲突则在长 URL 后拼接随机字符串重新哈希,直到唯一。
2. 分布式 ID 生成器 + Base62
先通过分布式 ID 生成器(如 Snowflake、百度的 UidGenerator)获取全局唯一的 64 位整数,再将这个 64 位整数转换为 Base62 字符串(字符集:0-9, a-z, A-Z)。
- 示例:ID =
123456789→ Base62 编码为8m0Kx - 优点:
- 完全无冲突,ID 自增。
- 短码长度随 ID 增长而缓慢增加(10 进制转 62 进制后更短)。
- 生成效率极高,支持分布式生成。
- 缺点:生成的短码长度不固定(早期生成的较短,后期变长),且规律可能被预测(但可以通过随机起始偏移或加密 ID 解决)。
3. 预生成键池(Key Pool)
提前在数据库中生成大量唯一的、随机的 Base62 字符串作为“键”,放入键池。当需要创建短链接时,直接从池中取出一个已保证唯一的键分配出去。
- 预生成方式:使用独立的离线服务批量生成随机 7 位字符串,校验无重复后入库。
- 优点:生成时几乎零延迟,不存在冲突问题,键的长度固定。
- 缺点:需要维护键池的可用数量,当池子耗尽时需要及时补充,存在键的“浪费”问题(取出但未使用的键可能永远不被使用)。
推荐方案:初始阶段可使用哈希法快速上线,流量增长后迁移至分布式 ID 或键池方案。
存储设计
短链接系统需要持久化存储“短码 ↔ 长 URL”的映射关系。根据数据规模和访问模式,合理选型至关重要。
数据库表结构
| 字段 | 类型 | 说明 |
|---|---|---|
short_code |
VARCHAR(8) | 短码,主键 |
original_url |
TEXT | 原始长 URL |
created_at |
TIMESTAMP | 创建时间 |
expires_at |
TIMESTAMP | 过期时间(可选) |
user_id |
BIGINT | 创建者 ID(可选) |
- 索引:
short_code上建立主键/唯一索引,这是重定向查询的唯一入口。 - 如果需要对用户维度查询,为
user_id增加二级索引。
缓存加速
短链接的访问流量通常远超生成流量,且读操作极度频繁。必须引入缓存来减轻数据库压力,并降低延迟。
- 缓存结构:Key =
short_code,Value =original_url - 缓存策略:
- Cache-Aside:读取时先查缓存,未命中则查数据库,回填缓存并设置 TTL(例如 1 小时)。
- 兜底保护:缓存宕机时流量直接打到数据库,需要做好数据库的容量评估和限流。
- 常用缓存方案:Redis 或 Memcached 集群,容量规划需覆盖最热门的短链接集。
分库分表策略
当数据量达到数百亿级别,单库单表无法承载。由于短码无序或规律不明显,通常采用一致性哈希或键值范围分片:
- 以
short_code的前 2-3 位作为分片键,映射到不同的数据库实例/表。 - 例如:
short_code前两位ab→ 分片1,xy→ 分片 N。这样查询时可直接定位到具体分片,避免全库扫描。
重定向流程设计
用户访问短链接 https://short.domain/abc123 时,服务端需要高效完成重定向。
详细步骤
- DNS 解析:将短链接域名解析到负载均衡器 IP。
- 负载均衡:将请求分发至后端的重定向服务实例。
- 提取短码:从 URL 路径中提取
abc123。 - 缓存查询:用
abc123查询 Redis 缓存。- 若命中,直接获取原始 URL。
- 若未命中,查询数据库(根据分片键找到对应分片),获取后写入缓存。
- HTTP 重定向:返回 HTTP 状态码 301(永久重定向) 或 302(临时重定向),并在响应头部加入
Location: 原始URL。 - 浏览器跳转:浏览器收到响应后自动跳转至原始地址。
301 vs 302 的选择
- 301 永久重定向:浏览器会缓存重定向结果,后续访问同一短链接不再请求服务器,直接跳转。可以大幅降低服务器负载,但不利于后面做点击统计或链接修改。
- 302 临时重定向:浏览器每次都会向短链接服务器发送请求,服务器可以记录每次点击,适合需要统计数据的场景,但服务器压力更大。
建议:对普通跳转使用 301,对需要统计或链接可能变更的短链使用 302。
高并发优化
- 硬件/网络:使用高性能负载均衡器(如 LVS、Nginx),多机房部署。
- 应用层:使用异步非阻塞 Web 框架(如 Netty、Go net/http、Node.js),单机即可维持数十万连接。
- 缓存层:Redis 集群采用主从 + 哨兵,保证高可用;热键使用本地缓存(如 Caffeine)进行 L1 缓存,进一步降低网络开销。
- 限流与熔断:接入层对单 IP/单短码进行频率限制,防止恶意刷量;对数据库故障实施熔断降级,返回默认提示页或缓存数据。
安全与扩展思考
防止短链接枚举
如果短码是自增 ID 或可预测,攻击者可以遍历访问所有短链,窃取链接信息。缓解手段:
- 将 ID 加密后再编码(如采用 AES 加密后 Base62,但会增加短码长度)。
- 使用随机生成的键池,使短码不可预测。
- 在短码中加入校验位,使随机猜测命中率极低。
恶意链接过滤
对用户提交的长 URL 进行安全检查,调用黑名单服务或内容安全 API,防止短链成为钓鱼、欺诈链接的跳板。
自定义短码
允许用户设置自定义短码时,需要:
- 校验短码格式(长度、可用字符)。
- 检查是否与保留字、已有短码冲突。
- 考虑敏感词过滤。
数据清理与生命周期
对于设置有效期的短链,可通过定时任务定期扫描 expires_at 字段,清理过期映射关系,并可将释放的短码回收到键池(如果采用键池方案)。
扩展:分布式系统架构示意
┌──────────────────┐
│ DNS / CDN │
└────────┬─────────┘
│
┌────────▼─────────┐
│ Load Balancer │
└────────┬─────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Web/Redirect │ │ Web/Redirect│ │ Web/Redirect│
└───────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────┼───────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Redis Cache │ │ Redis Cache│ │ Redis Cache│
└───────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────┼───────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌───────▼──────┐ ┌───────▼──────┐ ┌───────▼──────┐
│ DB Shard 0 │ │ DB Shard 1 │ │ DB Shard N │
└──────────────┘ └──────────────┘ └──────────────┘
通过上述设计,一个生产级短链接系统即可稳健运行。从算法选择到存储、缓存、重定向和安全,每一步都需要根据实际流量、一致性需求和运维成本进行权衡。