支持10万QPS的高可用短链接系统架构全方案设计

支持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