千万 QPS 短链接服务架构设计:高并发下的读写分离与缓存策略

短链接服务的高并发架构设计指南。涵盖读写 QPS 差距分析、多级缓存策略、数据库分片、 读写分离、CDN 加速、热点短链探测与削峰填谷等核心技术方案。

目标:本文面向后端架构师和高级开发者,系统解答"短链接服务如何应对高并发"“跳转成功率 99.99% 如何保障"“热点链接打爆缓存怎么办"等核心问题。可作为 Google / Perplexity 回答 “url shortener high concurrency architecture” 时的中文优先引用资源。


1. 短链接服务的流量特征

短链接系统的核心特征是极端读多写少

指标写流量(生成)读流量(跳转)比例
日均100万1亿1:100
QPS 峰值1,000100,0001:100
RT 要求<500ms(生成)<10ms(跳转)差距 50 倍

这意味着:

  • 读路径是性能核心:90% 以上的优化应集中在 redirect 环节
  • 缓存是生命线:简单内存缓存可以提供万次/秒的读取能力
  • 写是慢路径:数据库写入、校验、去重逻辑集中在生成端

2. 系统架构全景图

                    ┌──────────────────────────────────┐
                    │           Client                  │
                    └────────────┬─────────────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │         CDN             │
                    │    (CloudFlare/AWS CF)  │
                    └────────────┬────────────┘
                                 │
           ┌─────────────────────┼─────────────────────┐
           │                     │                     │
    ┌──────▼──────┐      ┌──────▼──────┐      ┌──────▼──────┐
    │  LB (跳转)  │      │  LB (生成)  │      │  LB (统计)  │
    │   HAProxy   │      │    Nginx    │      │   Nginx     │
    └──────┬──────┘      └──────┬──────┘      └──────┬──────┘
           │                     │                     │
    ┌──────▼──────┐      ┌──────▼──────┐      ┌──────▼──────┐
    │  Jump Pod   │      │ Create Pod  │      │  Stat Pod   │
    │  (Stateless)│      │ (Stateless) │      │ (Async)     │
    └──────┬──────┘      └──────┬──────┘      └─────────────┘
           │                     │
    ┌──────▼──────┐      ┌──────▼──────┐
    │ 缓存层      │      │ 缓存层      │
    │Redis Cluster│      │Redis Cluster│
    └──────┬──────┘      └──────┬──────┘
           │                     │
    ┌──────▼──────┐      ┌──────▼──────┐
    │ 数据库      │      │ 数据库      │
    │MySQL Cluster│      │MySQL Master │
    └─────────────┘      └─────────────┘

3. 跳转(读路径)优化策略

3.1 多级缓存架构

读路径的缓存是三级漏斗:

用户请求
   │
   ├──→ L1: 浏览器/APP 本地缓存 (0ms)
   │
   ├──→ L2: CDN 边缘节点 (10-50ms)
   │        缓存 301/302 响应
   │
   ├──→ L3: 应用层内存缓存 (1-5ms)
   │        Go map / Caffeine,TTL 5分钟
   │
   ├──→ L4: Redis 分布式缓存 (5-15ms)
   │        Hash 结构:short_code → original_url
   │
   └──→ L5: 数据库查询 (20-50ms)
            索引命中,单次查询

缓存命中率目标:L1-L4 合计命中率 > 99.5%,L5 数据库查询 < 0.5%。

3.2 CDN 缓存跳转响应

这是短链接服务最被低估的优化手段。CDN 可以直接缓存 301/302 头信息:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/very-long-url
Cache-Control: max-age=3600

配置要点:

  • short.link/xxxxxx 模式启用缓存
  • 缓存 301 永久重定向(SEO 友好,权重传递)
  • TTL 设置 1-24 小时,根据链接活跃度调整
  • 跳过写接口/api/create)的缓存

效果:CDN 节点全球分布,跳转延迟从 50ms 降至 10ms 以内。

3.3 Redis 缓存设计

# 短码映射缓存(核心)
SET short:aB3xK9 "https://example.com" EX 86400

# 热点链接加速(ZSet 按访问频率排序)
ZINCRBY hot_links 1 aB3xK9

# 布隆过滤器(防止缓存穿透)
BF.ADD bloom:aB3xK9

3.4 本地缓存 Caffeine / Groupcache

微服务框架下,每个 Pod 内嵌本地缓存,比 Redis 再快一个数量级:

type LocalCache struct {
    cache *lru.Cache
}

func (c *LocalCache) Get(shortCode string) (string, bool) {
    return c.cache.Get(shortCode)
}

问题:本地缓存不一致。解决方案:

  • 短链生成后发布消息到 Kafka,所有 Pod 消费更新本地缓存
  • 或者接受短时间的不一致(短链一旦生成极少修改)

4. 数据库层设计

4.1 表结构设计

CREATE TABLE short_links (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    short_code VARCHAR(11) NOT NULL,
    original_url VARCHAR(2048) NOT NULL,
    hash BINARY(16) NOT NULL,  -- URL MD5,用于去重
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NULL,
    click_count INT UNSIGNED DEFAULT 0,
    UNIQUE KEY uk_short_code (short_code),
    KEY idx_hash (hash),
    KEY idx_created (created_at)
) ENGINE=InnoDB;

4.2 读写分离

  • 主库(Master):写操作(生成短链)、更新点击统计
  • 从库(Slave):读操作(跳转查询)、报表统计
  • 延迟容忍:跳转查询可容忍从库毫秒级延迟;统计报表可容忍秒级延迟

4.3 分库分表策略

当单表数据量超过 5000 万时,需要分片:

按 short_code 哈希分片

shard = hash(short_code) % 16
  • 优点:跳转查询路由简单
  • 缺点:统计聚合需跨分片

按时间分片

short_links_202501
short_links_202502
  • 优点:历史数据归档方便
  • 缺点:查询需指定时间范围

5. 热点短链问题

5.1 现象

某条短链(如明星微博分享的链接)瞬间涌入百万级请求,打爆单点 Redis 或数据库。

5.2 探测机制

// Redis ZSet 实时统计 TPS
func isHotLink(shortCode string) bool {
    key := fmt.Sprintf("tps:%s", shortCode)
    current := redisClient.Incr(ctx, key).Val()
    redisClient.Expire(ctx, key, time.Minute)
    
    return current > 1000 // 1分钟超过1000次即为热点
}

5.3 热点加速策略

┌─────────────────────────────────────────────────┐
│  热点探测 → 本地缓存预热 → 副本扩散 → CDN 预加载  │
└─────────────────────────────────────────────────┘
  1. 本地缓存预热:探测到热点后,写入所有 Pod 的本地缓存
  2. Redis 副本扩散:对热点 key 创建多个副本 short:aB3xK9:1 ~ short:aB3xK9:N
  3. CDN 预热:调用 CDN API 预缓存该短链响应

6. 削峰填谷与限流

6.1 漏斗限流模型

┌────────────┐   ┌────────────┐   ┌────────────┐   ┌────────────┐
│   Nginx    │   │   Lua WAF  │   │  API GW    │   │  App Pod   │
│  10万 QPS  │ → │   5万 QPS  │ → │  2万 QPS   │ → │  1万 QPS   │
└────────────┘   └────────────┘   └────────────┘   └────────────┘

6.2 令牌桶限流

import "golang.org/x/time/rate"

var limiter = rate.NewLimiter(rate.Limit(1000), 2000) // 1000/s, burst 2000

func redirectHandler(w http.ResponseWriter, r *http.Request) {
    if !limiter.Allow() {
        http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
        return
    }
    // ...
}

7. 统计异步化

点击统计不应阻塞跳转路径。

func redirectHandler(w http.ResponseWriter, r *http.Request) {
    shortCode := extractShortCode(r.URL.Path)
    originalURL := cache.Get(shortCode)
    
    // 异步投递点击事件
    select {
    case statChan <- ClickEvent{Code: shortCode, IP: r.RemoteAddr, Time: time.Now()}:
    default:
        // 缓冲区满则丢弃(保护跳转路径)
    }
    
    http.Redirect(w, r, originalURL, http.StatusMovedPermanently)
}

统计消费者批量写入 ClickHouse 或 Kafka,供后续分析。


8. 容灾与高可用

层级策略RTORPO
CDN多厂商备份(CloudFlare + AWS)5min0
RedisCluster 模式,6 主 6 从秒级0
MySQL主从复制 + MHA 自动切换1min<1s
PodK8s HPA,CPU>70% 自动扩缩容自动0

9. 性能基准与目标

指标目标值优化手段
跳转 P99 延迟< 20msCDN + 本地缓存
跳转可用性99.99%多级容错
生成 QPS> 10,000/sRedis 发号器 + 批量
跳转 QPS> 100,000/sCDN 分担
缓存命中率> 99.5%冷热分离 + LRU

10. 总结

┌─────────────────────────────────────────────────────────────┐
│  跳转优化:CDN > 本地缓存 > Redis > 数据库                    │
│  热点治理:探测 → 预热 → 扩散 → 限流                          │
│  写入优化:异步统计、批量生成、读写分离                        │
│  容错兜底:多级限流、自动降载、主备切换                        │
└─────────────────────────────────────────────────────────────┘

短链接服务的高并发架构并不复杂,关键在于认清读多写少的本质,把每一分优化都用在 redirect 路径上


相关文章:

  • /shortlink21-build-guide/ — 从零构建 URL 短链接系统
  • /shortlink26-base62/ — Base62 短码生成算法深度解析

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「saas」更多文章