
支持10万QPS的高可用短链接系统架构全方案设计
本文面向中大型互联网业务场景,从领域驱动拆分、架构选型、数据策略、容灾设计到落地代码骨架,完整拆解高并发短链接跳转系统的设计思路,明确每个选型的收益与权衡点,兼顾性能、成本与可扩展性,可直接作为团队落地的参考方案。
寻
寻觅~流光
54 天前
支持10万QPS的高可用短链接系统架构全方案设计### 🚀 1. 执行概要与架构范式
我们最终选择「事件驱动分层微服务架构」作为核心范式,放弃传统单体短链服务的设计:核心链路(短链跳转)完全剥离非核心逻辑,仅保留毫秒级响应能力,短链生成、数据统计、过期清理等非实时逻辑全部异步解耦,在不超过3台8C16G云服务器的资源量级下即可支撑10万QPS的跳转请求,同时满足99.99%的可用性要求。
### 🧱 2. 系统组件设计(C4 Level 2)
整体组件交互链路如下:
| 组件层级 | 核心组件 | 职责说明 |
| --- | --- | --- |
| 接入层 | CDN+WAF+SLB | 承接公网流量,过滤恶意请求,做全局流量负载,热点短链直接在CDN层缓存响应 |
| 网关层 | 云原生API网关 | 实现限流、鉴权、TLS卸载,将跳转路由转发至核心服务,短链生成请求路由至管控服务 |
| 核心服务层 | 短链跳转服务 | 无状态设计,仅负责短链到长链的映射查询与302重定向,无任何复杂业务逻辑 |
| 核心服务层 | 短链管控服务 | 承接短链生成、修改、删除、配置过期规则等管理类请求 |
| 异步层 | 事件总线(Kafka) | 所有跳转埋点事件、短链变更事件全部异步投递,不阻塞核心链路 |
| 下游服务层 | 统计分析服务 | 消费Kafka事件,落地访问数据做UV/PV统计、地域分布分析 |
| 下游服务层 | 过期清理服务 | 定时扫描过期短链,清理缓存与冷数据,释放存储资源 |
整体交互流:用户请求短链域名 → CDN优先返回缓存结果 → 未命中则到网关 → 跳转服务优先查本地堆缓存+分布式缓存 → 未命中查数据库 → 返回302重定向到目标长链。
### 💾 3. 数据与存储架构
我们根据访问热度做分层存储策略,完全避免热点击穿问题:
1. 缓存层:Top 1% 超热短链直接存储在跳转服务的Caffeine本地堆缓存,无需跨网络访问Redis,响应延迟控制在1ms以内;全量活跃短链映射关系存储在Redis集群,开启RDB+AOF持久化避免缓存全量丢失。
2. 持久化层:短链元数据采用MySQL分库分表策略,以短链字符哈希值作为分片键,拆分到16个库256张表中,单表数据量控制在1000万以内,保证查询性能。
3. 统计层:全量跳转事件落地到ClickHouse,支持亿级数据下的秒级聚合查询,满足运营分析需求。
一致性策略:核心映射链路采用最终一致性,短链变更后主动淘汰缓存,保证最多1s的窗口内可能出现旧映射,完全符合短链业务的容忍度。
### ⚖️ 4. 权衡与非功能设计
- 可扩展性与性能:所有核心服务完全无状态,支持水平扩容,单节点可支撑2万QPS,扩容到5个节点即可满足10万QPS要求,热点短链在CDN层即可拦截70%以上流量,大幅降低后端负载。
- 可用性与容灾:核心链路配置Sentinel限流熔断,当缓存集群故障时自动降级查询数据库集群,配置多级重试机制避免偶发网络错误,全链路无单点故障,可用性可达99.99%。
- 权衡矩阵:我们选择AP(高可用、分区容错)优先于CP(强一致性),牺牲了短链变更时小于1s的瞬时数据一致性,换取核心链路的极高可用性;同时我们放弃了自增ID转短链的方案,选择预生成短链池,避免高并发下数据库自增ID的性能瓶颈,仅额外占用少量内存存储预生成的短链即可。
### 💻 5. 核心实现骨架
下面是短链生成与跳转的核心Java代码骨架:
```java
// 短链生成工具类
public class ShortLinkGenerator {
// 可用字符集,共62位,生成6位短链可支持62^6=568亿的容量
private static final String CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final int SHORT_LINK_LENGTH = 6;
// 从预生成短链池取出唯一短链
public String generateUniqueShortLink() {
// 从Redis预生成的短链池中pop一个可用值,避免重复
return redisTemplate.opsForSet().pop("short_link_pool");
}
// 进制转换生成短链
private String encode(Long num) {
StringBuilder sb = new StringBuilder();
while (num > 0) {
sb.append(CHARS.charAt((int) (num % CHARS.length())));
num = num / CHARS.length();
}
return sb.reverse().toString();
}
}
// 跳转服务核心接口
@RestController
public class RedirectController {
@GetMapping("/{shortCode}")
public void redirect(@PathVariable String shortCode, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 先查本地Caffeine缓存
String longUrl = caffeineCache.get(shortCode, key -> {
// 本地未命中查Redis
String url = redisTemplate.opsForValue().get("short:map:" + key);
if (url != null) return url;
// Redis未命中查数据库
ShortLinkPO po = shortLinkMapper.selectByShortCode(key);
if (po != null) {
redisTemplate.opsForValue().set("short:map:" + key, po.getLongUrl(), 7, TimeUnit.DAYS);
return po.getLongUrl();
}
return null;
});
if (longUrl == null) {
response.setStatus(404);
return;
}
// 异步投递访问事件,不阻塞主链路
kafkaTemplate.send("short_link_access_topic", shortCode + "|" + request.getRemoteAddr());
response.setStatus(302);
response.sendRedirect(longUrl);
}
}
```
阅读
3
分享
0
质量分
92