0Pricing
DSA Interview Prep · 课时

系统设计面试框架

学习 RADIO 五步框架(需求、API、数据、基础设施、优化),并练习将其应用于 URL 短链服务。

系统设计面试框架 是 CoddyKit 上的免费 DSA Interview Prep 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 DSA Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 DSA Interview Prep 课程共包含 4 节课。

系统设计在面试中的重要性

系统设计面试考查您在大规模场景下思考的能力——您会如何为数十亿用户设计推特、YouTube 或网址缩短服务?与只有一个正确答案的编程题不同,系统设计是开放式的:您必须做出权衡并说明理由。高级和专家级职位通常会专门用 30–45 分钟进行这一轮面试。

面试官会评估您是否能够明确需求、估算负载、提出高层次架构、深入关键组件并讨论权衡,同时保持清晰沟通。结构化框架可以防止您漫无边际地表达,并确保您覆盖所有关键维度。

# System design is not about a single correct answer.
# Interviewers look for:
eval_criteria = [
    'Ability to clarify requirements before designing',
    'Back-of-envelope capacity estimation',
    'High-level architecture with clear components',
    'Data modelling and storage choice',
    'Handling scalability (10x, 100x load)',
    'Trade-off discussion (consistency vs availability, etc.)',
    'Communication: talking through decisions as you make them',
]
for c in eval_criteria:
    print('-', c)

RADIO 框架概览

RADIO 框架为任何系统设计面试提供了一个可重复使用的五步结构:

  • R — 需求:功能性需求和非功能性需求
  • A — 接口设计:系统向外提供哪些操作?
  • D — 数据模型:存储哪些数据,以及如何存储?
  • I — 基础设施:高层次组件(服务器、队列、缓存)
  • O — 优化:瓶颈、缓存、分片、复制

始终按顺序执行这些步骤,但当出现新的洞察时,应返回并完善前面的步骤。每个阶段大致分配相等的时间。不要在澄清需求之前直接开始绘制方框。

RADIO = {
    'R': 'Requirements — What must the system do? What scale?',
    'A': 'API         — Define endpoints/operations the system exposes',
    'D': 'Data Model  — Entities, schemas, storage types',
    'I': 'Infra       — High-level architecture: servers, queues, caches, CDN',
    'O': 'Optimise    — Identify and address bottlenecks, trade-offs',
}
for step, desc in RADIO.items():
    print(f'[{step}] {desc}')

print('\nTiming guide for a 45-min interview:')
print('  R: 5 min | A: 5 min | D: 10 min | I: 15 min | O: 10 min')

步骤 R:明确需求

在明确需求之前,绝不要开始设计。询问功能性需求(系统要做什么)和非功能性需求(规模、延迟、可用性)。以网址缩短服务为例:

  • 功能性需求:缩短网址、重定向到原始网址,可选支持自定义别名和过期时间
  • 非功能性需求:每天需要处理多少个网址?读多还是写多?可用性要求是多少(99.9% 还是 99.99%)?可接受的延迟是多少?

明确说明假设能够体现成熟度。面试官经常会故意给出含糊的需求说明,以观察您是否会提出正确的问题。花两分钟澄清需求,可以避免设计出错误的系统。

# Requirements questions for URL Shortener:
functional = [
    'Shorten a given URL to a 7-character alias',
    'Redirect short URL to original URL',
    'Allow custom aliases (optional)',
    'URL expiry (optional)',
    'Analytics: click count per URL (optional)',
]
non_functional = [
    '100 million new URLs per day (write: ~1160/sec)',
    '10:1 read:write ratio => 11,600 redirects/sec',
    'Redirects must be < 100ms p99 latency',
    '99.99% availability (< 1 hr downtime/year)',
    'URLs must be globally accessible',
]
print('Functional:')
for f in functional: print(' -', f)
print('\nNon-functional:')
for nf in non_functional: print(' -', nf)

步骤 R:粗略容量估算

明确需求后,估算容量。这能体现您在提出解决方案之前分析系统规模的能力。需要算出的关键数据包括:每秒请求数(RPS)、每天或每年所需的存储空间、带宽,以及缓存所需的内存。

使用整齐的数字并进行大致估算即可。面试官关注的是数量级,而不是精确数值。例如,对于网址缩短服务:每天 100M 次写入 ÷ 86400 ≈ 每秒 1160 次写入。每天 10B 次读取 ÷ 86400 ≈ 每秒 115K 次读取。每条网址记录约为 500 字节:100M × 500B = 每天 50 GB,即每年 18 TB。

# Back-of-envelope for URL Shortener
writes_per_day = 100_000_000        # 100 million URLs/day
read_write_ratio = 100              # 100:1 read/write
reads_per_day = writes_per_day * read_write_ratio
bytes_per_url = 500                 # url string + metadata
years_to_store = 5

print('=== Capacity Estimation ===')
print(f'Writes/sec:  {writes_per_day / 86400:.0f}')
print(f'Reads/sec:   {reads_per_day / 86400:,.0f}')
print(f'Storage/day: {writes_per_day * bytes_per_url / 1e9:.1f} GB')
print(f'Storage total ({years_to_store}y): {writes_per_day * bytes_per_url * 365 * years_to_store / 1e12:.1f} TB')

cache_hit_rate = 0.80
hot_urls = reads_per_day * (1 - cache_hit_rate)
print(f'\n80% cache hit rate: {cache_hit_rate*100}% of reads from cache')
print(f'DB reads/sec: {hot_urls / 86400:,.0f}')

步骤 A:接口设计

定义接口范围——系统向客户端和内部服务提供的操作。明确指定请求方法、端点路径、请求参数和响应格式。这为其余设计奠定基础:其他所有部分都存在于这些接口的实现过程中。

对于网址缩短服务,两个核心接口是:(1) POST /shorten,用于创建短网址;(2) GET /{alias},用于重定向。可选接口包括:DELETE /{alias},用于删除;GET /{alias}/stats,用于分析。请指定响应状态码(201 已创建、301 重定向、404 未找到)。

# API Design for URL Shortener
apis = [
    {
        'method': 'POST',
        'path': '/api/v1/shorten',
        'request': '{"long_url": "https://...", "alias": "optional", "expires_at": "optional"}',
        'response': '201 Created: {"short_url": "https://short.ly/abc1234", "alias": "abc1234"}',
    },
    {
        'method': 'GET',
        'path': '/{alias}',
        'request': 'No body',
        'response': '301 Redirect to long_url (or 404 Not Found)',
    },
    {
        'method': 'GET',
        'path': '/api/v1/{alias}/stats',
        'request': 'Optional: date range query params',
        'response': '200 OK: {"clicks": 42000, "unique_visitors": 15000}',
    },
]
for api in apis:
    print(f'{api["method"]} {api["path"]}')
    print(f'  Request:  {api["request"]}')
    print(f'  Response: {api["response"]}')
    print()

步骤 D:数据模型

数据模型定义要存储哪些数据以及如何存储。确定核心实体及其属性。对于网址缩短服务:使用包含 alias(主键)、long_url、created_at、expires_at 和 user_id 的 urls 表。还可以使用可选的 clicks 表进行分析。

选择正确的存储类型至关重要:关系型数据库适合带有复杂查询的结构化数据;键值存储(Redis、DynamoDB)适合在大规模场景下进行 O(1) 别名查找;对象存储(S3)适合大型数据块。对于网址缩短服务,以别名为键的键值存储非常适合读取,同时可以使用关系型数据库处理写入和管理。

# Data model for URL Shortener

# Core table (PostgreSQL)
urls_schema = '''
CREATE TABLE urls (
    alias       VARCHAR(16) PRIMARY KEY,   -- e.g., 'abc1234'
    long_url    TEXT NOT NULL,
    user_id     UUID REFERENCES users(id),
    created_at  TIMESTAMP DEFAULT NOW(),
    expires_at  TIMESTAMP,
    click_count BIGINT DEFAULT 0
);
CREATE INDEX ON urls(user_id);
'''

# Cache layer (Redis) for hot reads
redis_schema = '''
alias  =>  long_url       # O(1) GET on cache hit
TTL = 24 hours (or until expiry)
Cache eviction: LRU
'''

print('PostgreSQL schema:')
print(urls_schema)
print('Redis cache:')
print(redis_schema)
print('Storage split: Redis for hot reads (~80%), PostgreSQL for writes and cold reads')

步骤 I:高层次基础设施

勾勒高层次的基础设施:哪些服务器负责哪些职责,数据如何在组件之间流动,以及使用了哪些外部服务。对于大规模网址缩短服务:

  • 负载均衡器:将流量分发到写入服务和读取服务的副本
  • 写入服务:生成别名、验证唯一性、写入数据库、使缓存失效
  • 读取/重定向服务:先检查 Redis 缓存,未命中时回退到数据库
  • 关系型数据库:权威数据源(带有只读副本)
  • Redis 集群:缓存热门网址映射,以支持亚毫秒级读取
# ASCII architecture sketch
architecture = '''
         Clients
            |
       [Load Balancer]
       /             \\
  [Write API]    [Read/Redirect API]
      |                  |  |
  [Alias Gen]       [Redis Cache]
      |                  |
  [PostgreSQL]---->[Read Replicas]

Alias Generation:
- Base62 encoding of auto-incrementing ID
- 62^7 = 3.5 trillion unique URLs (enough for 5 years at 100M/day)
- OR random 7-char Base62 with collision check
'''
print(architecture)
print('Key design decisions:')
print('  - Read/Write split: separate services for scalability')
print('  - Cache-aside pattern: read from Redis, fallback to DB')
print('  - 301 vs 302 redirect: 301 cached by browser (less load), 302 always hits server (analytics)')

步骤 O:优化并处理瓶颈

优化阶段负责处理瓶颈并扩展系统。对于网址缩短服务,主要关注点包括:重定向延迟(使用 CDN 或边缘缓存,让 Redis 靠近用户)、大规模场景下的别名唯一性(使用中央票据服务器,或使用带冲突检测的哈希),以及数据库写入瓶颈(批量写入,或通过队列进行异步写入)。

请明确讨论权衡:301 重定向可以降低服务器负载,但会损失分析数据的准确性;302 重定向可以进行跟踪,但会增加延迟。延长缓存过期时间可以降低数据库负载,但可能导致网址信息过时。展现您对这些矛盾的理解,能够体现高级别的思维能力。

# Key optimisation discussion points
optimisations = {
    'Read latency': [
        'Redis cluster in multiple regions (CDN-edge caching)',
        '301 redirect for non-tracked URLs (client caches)',
        '302 redirect when click analytics are needed',
    ],
    'Write throughput': [
        'Async writes: accept request, enqueue to Kafka, batch-commit to DB',
        'Ticket server (centralised ID generator) to avoid UUID collision',
        'Or: hash(user_id + timestamp + random) with retry on collision',
    ],
    'Availability': [
        'Multi-AZ PostgreSQL with automatic failover',
        'Redis Sentinel or Redis Cluster for HA cache',
        'Health checks + circuit breaker on each service',
    ],
    'Storage': [
        'Partition urls table by hash(alias) for horizontal scaling',
        'Archive expired URLs to cold storage (S3)',
    ],
}
for area, points in optimisations.items():
    print(f'{area}:')
    for p in points: print(f'  - {p}')
    print()

别名生成:62 进制编码

网址缩短服务的一个核心技术细节是如何生成简短且唯一的别名。标准方法是:使用数据库中的自增整数 ID,并将其编码为62 进制(数字 0-9、字母 a-z、A-Z)。7 个字符的 62 进制字符串可以表示 62^7 ≈ 3.5 万亿个唯一网址——按每天 1 亿个网址的速度,足够使用数十年。

这种方法不会发生冲突(每个 ID 都是唯一的),并且生成的字符串简短且适合网址使用。ID→别名的映射是确定且可逆的。权衡在于:连续的 ID 会生成可预测的别名(存在安全隐患)。可以打乱 62 进制字符表,或使用计数器偏移量来降低可预测性。

import string

BASE62_CHARS = string.digits + string.ascii_lowercase + string.ascii_uppercase
BASE = 62

def encode_base62(num):
    if num == 0:
        return BASE62_CHARS[0]
    result = ''
    while num:
        result = BASE62_CHARS[num % BASE] + result
        num //= BASE
    return result

def decode_base62(s):
    result = 0
    for c in s:
        result = result * BASE + BASE62_CHARS.index(c)
    return result

# Generate aliases for IDs 1, 100, 1000, 10^9
for id_ in [1, 100, 1000, 1_000_000, 10**9, 3_521_614_606207]:
    alias = encode_base62(id_)
    decoded = decode_base62(alias)
    print(f'ID {id_:20,} => alias "{alias}" (len={len(alias)}) => decoded={decoded}')

将 RADIO 应用于推特信息流设计

下面将 RADIO 简要应用于设计类似推特的新闻信息流,以展示该框架具有通用性:

  • R:用户发布推文、关注他人,并查看所关注用户发布的推文信息流。规模:3 亿用户、每天 5 亿条推文,信息流必须在 <2 秒内加载。
  • A:POST /tweets、GET /feed、GET /timeline/{user_id}
  • D:tweets 表(id、user_id、content、created_at);follows 表(follower_id、followee_id);每个用户的信息流缓存
  • I:扇出服务将新推文写入关注者的信息流(预先计算);使用 Cassandra 存储写入密集型的关注关系和推文表;使用 Redis 存储信息流缓存
# Fan-out on Write vs Fan-out on Read trade-off
fan_out_strategies = {
    'Fan-out on Write (Push)': {
        'How': 'When user A posts, immediately write to all followers feeds',
        'Pro': 'O(1) feed read — feed is precomputed',
        'Con': 'Celebrities with 10M followers => 10M writes per post; very slow write',
        'Best for': 'Users with few followers (regular users)',
    },
    'Fan-out on Read (Pull)': {
        'How': 'When user reads feed, query followees tweets and merge',
        'Pro': 'Writes are fast (one DB write per tweet)',
        'Con': 'Feed read is slow: must query all N followees',
        'Best for': 'Celebrity accounts (few reads per post)',
    },
    'Hybrid': {
        'How': 'Push for regular users, pull for celebrities',
        'Pro': 'Balances read and write cost',
        'Con': 'More complex implementation',
        'Best for': 'Production systems like Twitter',
    },
}
for strategy, details in fan_out_strategies.items():
    print(f'{strategy}:')
    for k, v in details.items(): print(f'  {k}: {v}')
    print()

系统设计面试中的常见错误

请避免以下会导致系统设计面试失利的常见错误:

  • 急于提出解决方案:在澄清需求前就开始画框图,表明工程习惯不佳
  • 不做估算:不知道规模就开始设计,无异于猜测
  • 过度设计:题目明确说有 10,000 个用户,却按 10 亿用户进行设计,会浪费面试时间
  • 不讨论权衡:每个选择都有优点和缺点;不提及这些内容,说明理解流于表面
  • 沉默不语:面试官需要了解您的思考过程;请在做出决策时同步讲述您的思考
# Checklist: before you stop talking, verify you covered:
checklist = [
    '[ ] Asked clarifying questions about scale and constraints',
    '[ ] Made capacity estimates (RPS, storage, bandwidth)',
    '[ ] Defined the API surface clearly',
    '[ ] Described the data model and storage choices',
    '[ ] Drew a high-level architecture with named components',
    '[ ] Identified the main bottleneck and proposed a solution',
    '[ ] Discussed at least one trade-off explicitly',
    '[ ] Verified the design meets the stated requirements',
]
for item in checklist:
    print(item)

快速测验

请测试您对本课数据结构与算法——编程面试准备相关概念的理解。

课程回顾

本课您学到了:RADIO 框架将系统设计面试划分为需求、应用程序接口、数据模型、基础设施和优化五个部分,在提出设计方案前始终澄清功能需求和非功能需求,并进行容量估算,以及明确讨论权衡——每个设计选择都有优点和缺点,面试官希望您清楚说明这些内容。接下来,我们将根据访问模式、一致性和规模,探讨如何在 SQL 和 NoSQL 存储引擎之间进行选择。

常见问题解答

「系统设计面试框架」课时是免费的吗?

是的 — 「系统设计面试框架」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 DSA Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 DSA Interview Prep 课程共包含 4 节课。

「系统设计面试框架」这节课中我会学到什么?

学习 RADIO 五步框架(需求、API、数据、基础设施、优化),并练习将其应用于 URL 短链服务。 你通过在浏览器中直接运行的动手代码来练习 DSA Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 DSA Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 DSA Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「系统设计面试框架」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 DSA Interview Prep 课中编写并运行代码吗?

能。每节 DSA Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 系统设计面试框架
  2. 可扩展数据存储:SQL 与 NoSQL
  3. 缓存、CDN 与负载均衡
  4. 设计限流器与 Twitter 信息流
← 返回 DSA Interview Prep