跳到主要内容

短链接系统设计

问题

如何设计一个像 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 = 62662^6 ≈ 568 亿个短码,对绝大多数场景足够。如果不够,扩展到 7 位即 62762^7 ≈ 3.5 万亿。

Q3: 高并发下发号器如何设计?

答案

  • 号段模式:每次从数据库批量取一段 ID(如 1000 个),用完再取,减少数据库压力
  • 雪花算法:分布式 ID 生成,单机 QPS 可达百万

详见 分布式 ID 生成

相关链接