系统设计面试框架
学习 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 反馈 — 无需本地设置。