短链接系统设计
问题
如何设计一个像 bit.ly 这样的短链接服务?
答案
核心需求
- 长 URL → 短 URL(如
https://s.cn/abc123) - 短 URL → 301/302 重定向到长 URL
- 高可用、高性能
整体架构
短码生成方案
| 方案 | 原理 | 优缺点 |
|---|---|---|
| 哈希截取 | MD5/MurmurHash 取前 6 位 | 可能冲突 |
| 发号器(推荐) | 自增 ID → Base62 编码 | 无冲突、有序 |
| 随机生成 | 随机字符串 + 查重 | 性能差 |
发号器 + Base62(推荐方案)
自增 ID → Base62 短码
public class ShortUrlService {
private static final String BASE62 =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 自增 ID 转 Base62 字符串
public String idToBase62(long id) {
StringBuilder sb = new StringBuilder();
while (id > 0) {
sb.append(BASE62.charAt((int) (id % 62)));
id /= 62;
}
return sb.reverse().toString(); // 6 位 Base62 可表示 62^6 ≈ 568 亿
}
public String createShortUrl(String longUrl) {
// 1. 通过发号器获取唯一 ID(雪花算法/号段模式)
long id = idGenerator.nextId();
// 2. 转为短码
String code = idToBase62(id);
// 3. 存储映射关系
save(code, longUrl);
return "https://s.cn/" + code;
}
}
重定向流程
短链跳转
@GetMapping("/{code}")
public void redirect(@PathVariable String code, HttpServletResponse response)
throws IOException {
// 1. 先查 Redis 缓存
String longUrl = redisTemplate.opsForValue().get("short:" + code);
// 2. 缓存未命中查数据库
if (longUrl == null) {
longUrl = urlMapper.findByCode(code);
if (longUrl != null) {
redisTemplate.opsForValue().set("short:" + code, longUrl, 24, TimeUnit.HOURS);
}
}
if (longUrl != null) {
response.sendRedirect(longUrl); // 302 临时重定向(方便统计点击)
} else {
response.sendError(404);
}
}
301 vs 302
- 301 永久重定向:浏览器缓存,后续不再请求短链服务 → 节省服务器压力,但无法统计点击
- 302 临时重定向:每次都经过短链服务 → 可以统计点击量,推荐
常见面试问题
Q1: 如何防止同一长 URL 生成多个短码?
答案:
用布隆过滤器或数据库唯一索引。也可以对长 URL 做哈希查表,如果已存在就直接返回。但实际业务中,允许同一长 URL 生成多个短码(不同渠道投放需要独立统计)更常见。
Q2: 短码用 6 位够吗?
答案:
6 位 Base62 = ≈ 568 亿个短码,对绝大多数场景足够。如果不够,扩展到 7 位即 ≈ 3.5 万亿。
Q3: 高并发下发号器如何设计?
答案:
- 号段模式:每次从数据库批量取一段 ID(如 1000 个),用完再取,减少数据库压力
- 雪花算法:分布式 ID 生成,单机 QPS 可达百万
详见 分布式 ID 生成。