短链系统设计
短链系统设计
把 https://www.example.com/very/long/url?with=params&more=stuff 变成 https://s.ly/abc123,背后是一套值得深挖的工程体系
短链系统是系统设计面试的经典入门题,看似简单,实则涉及发号器设计、缓存策略、高可用架构等多个核心主题。本文按照 RADIO 框架完整拆解这道题。
一、需求澄清(面试第一步)
面试官给题后先问清楚,直接开始设计是减分项:
- 功能边界:是否需要支持自定义短码(如
s.ly/my-brand)?是否需要统计点击量?短链有有效期吗? - 规模估算:预计 QPS 是多少?toC 百亿级还是 toB 中小规模?
- 一致性要求:统计数据需要实时准确还是允许几秒延迟?
本文假设:
- 不支持自定义短码(降低复杂度)
- 需要统计点击量(准实时,延迟 < 5 秒可接受)
- 短链默认有效期 1 年
- 规模:每天新增 1000 万条短链,日点击量 1 亿次
二、容量估算
写 QPS:1000 万 / 86400 ≈ 116 QPS(写压力不大)
读 QPS:1 亿 / 86400 ≈ 1157 QPS,峰值 ≈ 3500 QPS
短链数据库存储(1 年):
1000 万/天 × 365 天 = 36.5 亿条
每条记录约 500 字节(含原始 URL、短码、时间等)
36.5 亿 × 500B × 3(副本)≈ 5.5 TB(普通数据库 + 分表可承受)
Redis 缓存:
热点 URL 约占 20%(命中 80% 流量)
36.5 亿 × 0.2 × 100B ≈ 73 GB(Redis 集群可承受)三、核心设计:短码生成算法
生成短码是短链系统的核心问题,有三种主流方案:
方案一:自增 ID + Base62 编码(推荐)
数据库或发号器生成全局唯一自增 ID,然后转换为 Base62(a-z A-Z 0-9)字符串:
62^6 = 56,800,235,584(约 568 亿)→ 6 位短码可用 568 亿个
62^7 = 3.5 万亿 → 7 位可满足任意规模转换示例:12345678 → 5BAN (类似进制转换)
优点:唯一性 100% 保证,无碰撞风险,按 ID 顺序生成,索引友好。 缺点:自增 ID 暴露了系统规模(可以猜出你每天生成多少短链),安全敏感场景需要 ID 混淆。
方案二:哈希截取(简单但有碰撞)
对原始 URL 做 MurmurHash 或 MD5,取前 6 位 Base62 编码:
URL → MD5 (128 bit) → 取前 31 bit → Base62 → 6 位短码
碰撞概率约 0.03%(6800 万条后开始显著碰撞)碰撞时需要重哈希(加随机 salt 再试),性能有损耗。中小规模可用,大规模不推荐。
方案三:分布式发号器(生产推荐)
Snowflake(雪花算法):64 bit = 1 bit 符号位 + 41 bit 时间戳 + 10 bit 机器 ID + 12 bit 序列号。每毫秒每台机器可生成 4096 个唯一 ID,无中心化依赖。
号段模式:发号服务从 MySQL 批量取一段 ID(如每次取 1000 个),存本地内存慢慢用,减少数据库访问频率。美团 Leaf、百度 UidGenerator 都采用此方案。
推荐架构:号段模式发号器 → Base62 编码 → 短码,兼顾性能和可靠性。
四、API 设计
POST /api/v1/urls
请求体:{ "longUrl": "https://...", "expiryDays": 365 }
返回:{ "shortUrl": "https://s.ly/abc123", "shortCode": "abc123", "createdAt": "..." }
GET /{shortCode}
→ 302 临时重定向到原始 URL
GET /api/v1/urls/{shortCode}/stats
返回:{ "clicks": 12345, "uniqueVisitors": 8000, "topCountries": [...] }301 vs 302 重定向——高频追问
301 永久重定向:浏览器缓存,用户下次直接在本地跳转,不再请求服务器。好处:节省服务器压力。坏处:无法统计点击量(请求根本到不了服务器),且无法修改跳转目标(浏览器已缓存)。 302 临时重定向:每次都经过服务器,可以统计点击量,可以随时修改跳转目标(如短链过期后跳转到提示页)。结论:需要统计点击量必须用 302。
五、数据模型
-- 主表:URL 映射
CREATE TABLE url_mapping (
id BIGINT PRIMARY KEY, -- 用于 Base62 生成短码
short_code VARCHAR(10) NOT NULL UNIQUE, -- 短码(加唯一索引)
long_url TEXT NOT NULL, -- 原始 URL
created_at DATETIME NOT NULL,
expires_at DATETIME, -- NULL 表示永不过期
user_id BIGINT, -- 创建者(NULL 表示匿名)
INDEX idx_short_code (short_code),
INDEX idx_user_id (user_id)
);
-- 点击统计(写多读少,不放主表)
-- 通过 Kafka 异步写入,统计查询走 ClickHouse
CREATE TABLE click_stats (
short_code VARCHAR(10),
click_time DATETIME,
ip VARCHAR(50),
country VARCHAR(20),
referer TEXT
);六、缓存设计
读多写少(读 QPS 是写的 10 倍以上),缓存是重中之重:
Redis Hash 存储:key = url:{shortCode},field 存 longUrl、expiresAt 等。
缓存命中流程:
用户请求 /{shortCode}
↓
Redis 查询 url:{shortCode}
↓ 命中(80% 以上请求在此结束)→ 302 重定向,统计数据写 Kafka
↓ 未命中
查询数据库
↓
写入 Redis(TTL = min(短链过期时间, 1天))
↓
302 重定向缓存穿透防护:对于不存在的短码,缓存空值(TTL = 30 秒),防止恶意请求大量打到数据库。
热点短链:如果某个短链突然被大量传播(病毒营销),Redis 单 Key 可能成为热点。可以在应用层加 Caffeine 本地缓存(TTL = 1 分钟)作为 L1 缓存。
七、高可用架构
用户 → CDN(静态资源/部分热点 URL 缓存)
↓
Nginx 负载均衡 → 应用服务集群(无状态,水平扩展)
↓
Redis Cluster(3 主 3 从,Sentinel 故障转移)
↓
MySQL(主从,读写分离)
统计链路(异步):
应用服务 → Kafka → 统计消费者 → ClickHouse(OLAP 查询)数据库扩展:
- 短期:按 short_code 首字母分 16 张表(分表),扛住 36.5 亿记录
- 长期:按创建时间分库(冷热分离),冷数据归档到对象存储
八、面试题精选
Q:短链系统的读写比大概是多少?这对架构有什么影响?
短链是典型的读多写少场景(读写比约 10:1 甚至更高),因为每条短链创建一次,但可能被点击无数次。这决定了架构重心在缓存层——Redis 命中率做到 80% 以上,数据库压力就可以控制在很小范围内。写入路径相对简单,不是性能瓶颈。
Q:如何保证短码全局唯一?
推荐方案:分布式发号器(号段模式)生成全局唯一 ID,再转 Base62。号段模式下,每台应用服务器批量取一段 ID(如 1000 个)放本地,消耗完再取,MySQL 访问频率极低(每台服务器每用完 1000 个才访问一次),同时通过 DB 的唯一主键保证全局唯一性。
Q:短链过期如何处理?
两种方案:① 请求时检查(懒删除):Redis 设置 TTL,过期后查 DB,DB 返回已过期则跳转到提示页。② 定时扫描(主动删除):定时任务扫描 expires_at < now 的记录,删除数据库和缓存。推荐两种结合:Redis TTL 处理缓存过期,定时任务清理 DB 过期数据,节约存储空间。
知识星球深度内容
短链系统完整实现源码(含发号器、缓存设计、点击统计 ClickHouse 接入)、更多系统设计实战题,加入「AI 工程师加速社区」知识星球获取 👉 立即加入
