系统设计入门:2026 年如何设计 URL Shortener

发布: (2026年3月23日 GMT+8 13:06)
9 分钟阅读
原文: Dev.to

Source: Dev.to

系统设计面试让大多数开发者感到恐惧。问题开放式,范围庞大,而且没有唯一的“正确”答案。

但秘密是:每个系统‑design 问题都遵循相同的框架。一旦你用一个简单的例子学会了它,就可以将其应用到任何场景。

我们将使用 URL 短链接服务——最经典的系统‑design 问题——作为学习载体。

什么是系统设计?

系统设计是定义系统的架构、组件、模块、接口和数据流以满足给定需求的过程。

在面试中,你经常会被要求设计以下系统:

  • URL 短链接(bit.ly)
  • Twitter/X 时间线
  • YouTube
  • Uber 的乘车调度
  • Netflix 视频流

系统设计框架(6 步)

1. 明确需求
2. 估算规模
3. 定义 API
4. 设计数据模型
5. 绘制高层架构
6. 深入组件细节

让我们把每一步应用到 URL 缩短服务上。

步骤 1:明确需求

功能需求(系统要做什么)

  • 给定一个长 URL,生成一个短 URL(例如 bit.ly/abc123)。
  • 给定一个短 URL,重定向到原始的长 URL。
  • 短 URL 应在可配置的时间后失效(可选)。
  • 用户可以创建自定义短 URL(可选)。

非功能需求(系统要做到什么程度)

  • 高可用性 – 可用时间达 99.9 %。
  • 低延迟 – 重定向在

要点: 该系统以读为主,需要大量缓存,存储需求适中。

步骤 3:定义 API

POST /api/v1/urls
Request:
{
  "longUrl": "https://example.com/very/long/path",
  "expiresAt": "2027-01-01"
}
Response:
{
  "shortUrl": "https://bit.ly/abc123",
  "expiresAt": "2027-01-01"
}
GET /{shortCode}
Response: 301 Redirect to longUrl
DELETE /api/v1/urls/{shortCode}
Response: 200 OK

301 与 302 重定向

  • 301(永久) – 浏览器会缓存重定向 → 对我们服务器的请求更少 → 成本更低。
  • 302(临时) – 浏览器每次都会访问我们的服务器 → 可获得更精确的分析数据。

根据产品需求进行选择。

步骤 4:设计数据模型

表结构(SQL 示例)

CREATE TABLE urls (
  short_code   VARCHAR(7)    PRIMARY KEY,
  long_url     VARCHAR(2048) NOT NULL,
  created_at   TIMESTAMP    DEFAULT NOW(),
  expires_at   TIMESTAMP,
  user_id      BIGINT,               -- 匿名用户时可为空
  click_count  BIGINT       DEFAULT 0
);

CREATE INDEX idx_short_code ON urls(short_code);

short_code 为主键。下面列出三种常见的生成策略。

短码生成策略

选项 1 – 哈希 + 截断

import hashlib

def generate_short_code(long_url: str) -> str:
    hash_value = hashlib.md5(long_url.encode()).hexdigest()
    return hash_value[:7]          # MD5 的前 7 位字符

问题: 哈希冲突(不同的 URL 可能得到相同的短码)。


选项 2 – 基于计数器的 Base62 编码

BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def encode_base62(num: int) -> str:
    if num == 0:
        return BASE62[0] * 7
    result = []
    while num > 0:
        result.append(BASE62[num % 62])
        num //= 62
    return ''.join(reversed(result)).zfill(7)

工作原理: 在数据库中原子递增计数器,然后进行编码。保证唯一性。✅


选项 3 – 预生成密钥(密钥生成服务)

  • 预先填充 keys_available 表,包含数百万唯一的 7 字符串。
  • 当需要短 URL 时,取出一个密钥并移动到 keys_used
  • 完全消除冲突风险。

步骤 5:高层架构

[Client]


[Load Balancer]

   ├─── [Write Service] ──── [Primary DB]
   │                               │
   └─── [Read Service] ──── [Redis Cache] ──── [Read‑Replica DB]

写入路径

  1. 客户端 POST 一个长 URL。
  2. 写入服务生成短码(Base62 计数器或 KGS)。
  3. 将映射保存到主库。
  4. 将短 URL 返回给客户端。

读取路径

  1. 客户端 GET /{shortCode}
  2. 读取服务先查询 Redis(≈ 99 % 命中率)。
  3. 未命中时查询只读副本库。
  4. 发起 301/302 重定向。

步骤 6:深入探讨 – 缓存策略

每秒约 116 K 次读取,单靠数据库无法跟上;Redis 是必不可少的。

import redis

cache = redis.Redis(host='localhost', port=6379)
CACHE_TTL = 3600          # 1 小时

def get_long_url(short_code: str) -> str | None:
    # 1️⃣ 先尝试缓存
    cached = cache.get(f"url:{short_code}")
    if cached:
        return cached.decode()

    # 2️⃣ Cac
he miss → query DB
    long_url = db.query(
        "SELECT long_url FROM urls WHERE short_code = %s", short_code
    )

    # 3️⃣ Populate cache for future hits
    if long_url:
        cache.setex(f"url:{short_code}", CACHE_TTL, long_url)

    return long_url

Cache eviction policy: LRU(最近最少使用)。根据 80/20 法则,约 20 % 的 URL 承担约 80 % 的流量——缓存这 20 % 的热点。

常见后续问题

Q: How do you handle expired URLs?

  • Lazy deletion: 在读取时检查 expires_at;如果已过期,返回 410 Gone
  • Background job: 定期扫描表并删除过期行(或将其移动到归档)。

Q: How would you support custom aliases?

  • 验证请求的别名是否唯一且不含不当语言。
  • 直接将其存为 short_code;若冲突,返回错误。

Q: How can you make the system globally distributed?

  • 在多个地区部署读副本和 Redis 缓存。
  • 使用 geo‑DNS 或 anycast 负载均衡器将用户路由到最近的地区。
  • 采用一致性哈希方案生成键,以避免跨数据中心的冲突。

Q: How would you collect analytics (click counts, referrers, etc.)?

  • 通过消息队列(例如 Kafka)异步递增 click_count 列。
  • 将详细事件存入时序数据库或数据仓库,以便后续分析。

Q: What if the short‑code space (7 chars, Base62) runs out?

  • 切换到 8 位代码(62⁸ ≈ 2.18 × 10¹⁴ 种可能)。
  • 或在安全的宽限期后回收未使用/已过期的代码。

TL;DR

  1. Clarify 功能性和非功能性需求。
  2. Estimate 流量、存储和延迟需求。
  3. Define 干净、版本化的 API。
  4. Model 数据(URL ↔ short code)并决定生成策略。
  5. Sketch 高层架构,包含独立的写/读服务、主数据库、读副本和 Redis 缓存。
  6. Dive 关键组件(缓存、过期处理、分析、全局分布)。

遵循此框架,你就能应对任何系统设计面试——从经典的 URL 缩短服务开始。 🚀

ed URLs 每夜

问:如何防止滥用?

  • 对每个 IP 进行速率限制(Redis 滑动窗口计数器)
  • 阻止恶意 URL(Safe Browsing API)
  • 对匿名用户使用 CAPTCHA

问:如何扩展到每日 10 B 请求?

  • 增加读取副本
  • 地理分布(用于重定向的 CDN)
  • 对分布式缓存使用一致性哈希

问:自定义短码?

  • 保存前检查唯一性
  • 为保留/自定义码使用单独的表

面试官实际评估的内容

  • 沟通 – 你能清晰地阐述你的思路吗?
  • 权衡 – 你是否理解每个决定背后的原因?
  • 规模意识 – 你在动手之前会进行估算吗?
  • 深度 – 当被要求时,你能对至少一个组件进行深入探讨吗?

你不需要完美的答案。你需要引导对话,做出合理的选择,并解释你的推理

实践题目(难度递增)

  1. URL Shortener(本文)– 从这里开始
  2. Pastebin – 文本共享
  3. Rate Limiter – 经典 API 设计
  4. Twitter Timeline – 粉丝流问题
  5. Design YouTube – 视频存储 + 流媒体
  6. Design Uber – 实时匹配

在学习系统设计的同时管理自由职业项目和客户?
Freelancer OS 将一切组织起来 — CRM、项目、收入,全部在 Notion 中。一次性 €19

0 浏览
Back to Blog

相关文章

阅读更多 »

自信的汤

现在是凌晨2点14分。Production 已降级。你在寻找火焰的边缘,但根本没有边缘。已经过去四十分钟。代码编译成功,测试 a...

一致性思考

数据问题 今天我们将讨论数据问题,数据是否必须立即 100% 准确?大多数人会说是,数据…