写点什么

100 万 QPS 短链系统如何设计?

  • 2025-06-26
    福建
  • 本文字数:3570 字

    阅读完需:约 12 分钟

前言


凌晨两点,监控大屏突然飙红——短链服务 QPS 突破 80 万!数据库连接池告急,Redis 集群响应延迟突破 500ms。


这不是演习,而是某电商平台大促的真实场景。


当每秒百万级请求涌向你的短链服务,你该如何设计系统?


今天这篇文章跟大家一起聊聊 100 万 QPS 短链系统要如何设计?


希望对你会有所帮助。


1 短链系统的核心挑战


首先我们一起看看设计一个高并发的短链系统,会遇到哪些核心的挑战。


如下图所示:



百万 QPS 下的三大生死关:

  1. ID 生成瓶颈:传统数据库自增 ID 撑不住百万并发

  2. 跳转性能黑洞:302 重定向的 TCP 连接成本

  3. 缓存雪崩风险:热点短链瞬间击穿 Redis


2 短链生成


2.1 发号器的设计


发号器是短链系统的发动机。


方案对比:



分段发号器实现(Java 版):


public class SegmentIDGen {    private final AtomicLong currentId = new AtomicLong(0);    private volatile long maxId;    private final ExecutorService loader = Executors.newSingleThreadExecutor();
public void init() { loadSegment(); loader.submit(this::daemonLoad); }
private void loadSegment() { // 从DB获取号段:SELECT max_id FROM alloc WHERE biz_tag='short_url' this.maxId = dbMaxId + 10000; // 每次取1万个号 currentId.set(dbMaxId); }
private void daemonLoad() { while (currentId.get() > maxId * 0.8) { loadSegment(); // 号段使用80%时异步加载 } }
public long nextId() { if (currentId.get() >= maxId) throw new BusyException(); return currentId.incrementAndGet(); }}
复制代码


关键优化

  1. 双 Buffer 异步加载(避免加载阻塞)

  2. 监控号段使用率(动态调整步长)

  3. 多实例分段隔离(biz_tag 区分业务)


2.2 短链映射算法


短码映射将长 ID 转换成 62 进制的字符串。


转换原理:

2000000000 = 2×62^4 + 17×62^3 + 35×62^2 + 10×62 + 8            = "Cdz9a"
复制代码


原始 ID: 2000000000,转换为 62 进制的值为 Cdz9a。


// Base62编码(0-9a-zA-Z)public class Base62Encoder {    private static final String BASE62 =         "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";        public static String encode(long id) {        StringBuilder sb = new StringBuilder();        while (id > 0) {            sb.append(BASE62.charAt((int)(id % 62)));            id /= 62;        }        return sb.reverse().toString();    }        // 测试:生成8位短码    public static void main(String[] args) {        long id = 1_000_000_000L;        System.out.println(encode(id)); // 输出:BFp3qQ    }}
复制代码


编码优势:

  • 6 位短码可表示 62^6 ≈ 568 亿种组合

  • 8 位短码可表示 62^8 ≈ 218 万亿种组合

  • 无意义字符串避免被猜测


3 存储架构


3.1 数据存储模型设计



3.2 缓存层级设计



3.3 缓存击穿解决方案

// Redis缓存击穿防护public String getLongUrl(String shortCode) {    // 1. 布隆过滤器预检    if (!bloomFilter.mightContain(shortCode)) {        return null;    }        // 2. 查Redis    String cacheKey = "url:" + shortCode;    String longUrl = redis.get(cacheKey);    if (longUrl != null) {        return longUrl;    }        // 3. 获取分布式锁    String lockKey = "lock:" + shortCode;    if (redis.setnx(lockKey, "1", 10)) { // 10秒超时        try {            // 4. 二次检查缓存            longUrl = redis.get(cacheKey);            if (longUrl != null) return longUrl;                        // 5. 查数据库            longUrl = db.queryLongUrl(shortCode);            if (longUrl != null) {                // 6. 回填Redis                redis.setex(cacheKey, 3600, longUrl);            }            return longUrl;        } finally {            redis.del(lockKey);        }    } else {        // 7. 等待重试        Thread.sleep(50);        return getLongUrl(shortCode);    }}
复制代码


防护要点:

  • 布隆过滤器拦截非法短码

  • 分布式锁防止缓存击穿

  • 双重检查减少 DB 压力

  • 指数退避重试策略


4 跳转优化


4.1 Nginx 层直接跳转


server {    listen 80;    server_name s.domain.com;        location ~ ^/([a-zA-Z0-9]{6,8})$ {        set $short_code $1;                # 查询Redis        redis_pass redis_cluster;        redis_query GET url:$short_code;                # 命中则直接302跳转        if ($redis_value != "") {            add_header Cache-Control "private, max-age=86400";            return 302 $redis_value;        }                # 未命中转发到后端        proxy_pass http://backend;    }}
复制代码


性能收益:

  • 跳转延迟从 100ms 降至 5ms

  • 节省后端服务器资源

  • 支持百万级并发连接


4.2 连接池优化


连接池优化可以用 Netty 实现:


// Netty HTTP连接池配置public class HttpConnectionPool {    private final EventLoopGroup group = new NioEventLoopGroup();    private final Bootstrap bootstrap = new Bootstrap();        public HttpConnectionPool() {        bootstrap.group(group)            .channel(NioSocketChannel.class)            .option(ChannelOption.SO_KEEPALIVE, true)            .handler(new HttpClientInitializer());    }        public Channel getChannel(String host, int port) throws InterruptedException {        return bootstrap.connect(host, port).sync().channel();    }        // 使用示例    public void redirect(ChannelHandlerContext ctx, String longUrl) {        Channel channel = getChannel("target.com", 80);        channel.writeAndFlush(new DefaultFullHttpRequest(            HttpVersion.HTTP_1_1,             HttpMethod.GET,             longUrl        ));        // 处理响应...    }}
复制代码


优化效果:

  • TCP 连接复用率提升 10 倍

  • 减少 80%的 TCP 握手开销

  • QPS 承载能力提升 3 倍


5 百万 QPS 整体架构


百万 QPS 整体架构如下图所示:



核心组件解析:


1、接入层

  • CDN:缓存静态资源

  • Nginx:处理 302 跳转,本地缓存热点数据


2、缓存层

  • Redis 集群:缓存短链映射

  • 布隆过滤器:拦截非法请求


3、服务层

  • 短链生成:分布式 ID 服务

  • 映射查询:高并发查询服务


4、存储层

  • MySQL:分库分表存储映射关系

  • TiKV:分布式 KV 存储 ID 生成状态


6 容灾设计


6.1 限流熔断策略


基于 Sentinel 的熔断降级:

public class RedirectController {    @GetMapping("/{shortCode}")    @SentinelResource(        value = "redirectService",         fallback = "fallbackRedirect",        blockHandler = "blockRedirect"    )    public ResponseEntity redirect(@PathVariable String shortCode) {        // 跳转逻辑...    }        // 熔断降级方法    public ResponseEntity fallbackRedirect(String shortCode, Throwable ex) {        return ResponseEntity.status(503)            .body("服务暂时不可用");    }        // 限流处理方法    public ResponseEntity blockRedirect(String shortCode, BlockException ex) {        return ResponseEntity.status(429)            .body("请求过于频繁");    }}
复制代码


6.2 多级降级方案


使用多级降级方案:



保证服务的高可用。


6.3 数据分片策略


基于短码分库分表:


public int determineDbShard(String shortCode) {    // 取短码首字母的ASCII值    int ascii = (int) shortCode.charAt(0);    // 分16个库    return ascii % 16;}
public int determineTableShard(String shortCode) { // 取短码的CRC32值 CRC32 crc32 = new CRC32(); crc32.update(shortCode.getBytes()); // 每库1024张表 return (int) (crc32.getValue() % 1024);}
复制代码


这里成了 16 个库,每个库有 1024 张表。


7 性能压测数据对比



压测环境:32 核 64G 服务器 × 10 台,千兆内网


总结


百万 QPS 短链架构核心要点如图所示:



四大设计原则:

  1. 无状态设计:跳转服务完全无状态,支持无限扩展

  2. 读多写少优化:将读性能压榨到极致

  3. 分而治之:数据分片,流量分散

  4. 柔性可用:宁可部分降级,不可全线崩溃


真正的架构艺术不在于复杂,而在于在百万 QPS 洪流中,用最简单的路径解决问题。当你的系统能在流量风暴中优雅舞蹈,才是架构师的巅峰时刻。


文章转载自:苏三说技术

原文链接:https://www.cnblogs.com/12lisu/p/18945701

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
100万QPS短链系统如何设计?_数据库_不在线第一只蜗牛_InfoQ写作社区