Pastebin 让用户粘贴一块文本并拿回一个任何人都能打开阅读的短 URL。它是 URL 短链的近亲,有一个重要区别:不存一个小目标 URL,你存一个潜在很大的内容 blob。这把设计推向字节住哪的问题,且——因为粘贴被读得远多于被写——推向激进缓存。它是个很好的"中等"面试题,奖励元数据与内容的干净分离。
- 每个粘贴生成一个短 base-62 key(随机或从计数器),就像 URL 短链;仅在随机时检查冲突。
- 把内容存在 blob 存储,而非 DB 行——保留一个小元数据记录(key → blob 位置、过期、标志),把(可能大的)文本放进对象存储。
- 读多约 10:1+——在 Redis 缓存热粘贴并经 CDN 服务;大多数读绝不该碰数据库或 blob 存储。
- 粘贴写一次、不可变——这使它们平凡可缓存(无失效)且对 CDN 友好。
- 过期是一等功能——支持 TTL;用读时惰性删除和后台清扫器的组合清除。
- 滥用是真正的运维风险——对创建限流、扫描恶意软件/垃圾,并支持不公开/私有粘贴。
每个粘贴铸造一个短 base-62 key。在数据库保留一个小元数据行(key、blob 指针、过期、可见性),把实际文本存在对象存储(S3)——只对极小粘贴内联。因为粘贴不可变且读多,在 Redis 缓存热的并用 CDN 罩住一切。经 TTL 配惰性 + 定时清理过期。在写路径限流和扫描以对抗滥用。
┌──────────────┐ ┌───────────────┐
写/读 │ App / API │──▶│ 元数据 DB │ key→blob,
┌──────────┐ ────────▶│ 服务器 │ │ (KV / SQL) │ 过期、标志
│ 客户端 │ └──────┬───────┘ └───────────────┘
└────┬─────┘ │ ┌───────────────┐
│ 读(热) ├──────────▶│ blob 存储 │ 粘贴内容
│ │ │ (S3) │
▼ ┌────▼────┐ └───────────────┘
┌─────────┐ 未命中 │ 缓存 │ 热粘贴 (Redis)
│ CDN │◀───────────▶│ (Redis) │
└─────────┘ └─────────┘
第 1 步 — 澄清需求
功能:从一块文本创建一个粘贴并收到唯一 URL;按 URL 读粘贴;可选自定义别名;可选过期(如 10 分钟、1 天、永不);可见性(公开 / 不公开)。非功能:极读多、低读延迟、高可用、持久性(过期前不丢粘贴),和横向可扩展。预先约束粘贴大小——比如最多几 MB 文本、带硬上限——因为它驱动存储决策。粘贴写一次且不可变:你创建一个、从不编辑它(新编辑是新粘贴),这极大简化缓存。
第 2 步 — 容量估算
假设每天 1M 新粘贴(~12 写/秒平均)和 10:1 读写比(~120 读/秒平均,病毒粘贴峰值远高)。平均粘贴 10 KB → ~10 GB/天新内容,~3.6 TB/年(过期回收空间前)。写的数字适中但读路径必须处理粘贴走红时的尖锐尖峰——这正是缓存和 CDN 的用途。键空间:7 字符 base-62 key 给 62⁷ ≈ 3.5 万亿组合,绰绰有余。
第 3 步 — API 设计
POST /pastes {content, expiry?, custom_alias?, visibility?}
→ {key, url}
GET /pastes/{key} → {content, created_at, expiry}
DELETE /pastes/{key} → ok # 仅所有者
创建端点被限流和认证(或 captcha 把关)以遏制滥用;读端点公开且重度缓存。
第 4 步 — Key 生成
每个粘贴需要一个短、URL 安全的 key。两种标准方法,与 URL 短链相同:
| 方法 | 怎么做 | 取舍 |
|---|---|---|
| 随机 base-62 | 生成 7 个随机字符,检查 DB 冲突,重试 | 不可猜(对不公开好);需冲突检查 + 重试 |
| 计数器 / ID 生成 | 分布式计数器(如范围分配或 Snowflake)→ base-62 编码 | 构造上无冲突;但顺序键可猜/可枚举 |
对粘贴服务,随机键通常更受偏好,因为"不公开"粘贴依赖 URL 不可猜;顺序 ID 会让任何人枚举每个粘贴。一个常见改进是预生成 key 池:一个后台服务把未用的 key 铸进一张表,使写路径只弹出一个(热路径上无实时冲突重试循环)。
第 5 步 — 存储:分离元数据与内容
决定性决策。别把多 KB/MB 文本塞进关系行——它膨胀数据库、拖慢扫描、浪费 DB 的强项。而是拆分:
- 元数据——每个粘贴一个小记录(key、指向 blob 的指针、大小、创建/过期时间戳、可见性、所有者)。住在数据库(键值存储或分片 SQL),按 key 查找快。
- 内容——实际文本,存在按 key(或内容哈希)寻址的对象存储(S3)。对象存储便宜、持久、为大 blob 而建。
对极小粘贴(几百字节),到 blob 存储的往返可能比读本身成本更高。一个常见改进是把小内容直接内联在元数据行,只在超过一个大小阈值时才溢出到 blob 存储——两全其美。
第 6 步 — 数据模型
pastes (
key PK, # base-62 短 key
blob_url, # 指向 S3 的指针(内联则 null)
inline_text, # 小粘贴直接存这
size, visibility, owner_id,
created_at, expires_at # 为清理建索引
)
按 key 分片(哈希分区)使查找保持单分片、表横向扩展。expires_at 上的索引支持清理清扫。
第 7 步 — 读写路径
写:校验 + 限流 → 获取一个 key(从池)→ 存内容(小则内联,否则 PUT 到 blob 存储)→ 插入元数据行 → 返回 URL。读:在缓存查 key;命中则立即返回;未命中则读元数据、取内容(内联或从 blob 存储)、填充缓存、返回。因为内容不可变,缓存条目从不需要失效——它只需在粘贴过期时过期。
第 8 步 — 缓存与 CDN
这是读多服务的核心。在元数据 + blob 查找前放一个 Redis 缓存,按粘贴 key 为键,使热门粘贴从内存服务。因为粘贴不可变,也用 CDN 罩住读路径:一个粘贴的渲染/原始内容能以长 TTL(由粘贴自己的过期限制)在边缘位置缓存,使病毒粘贴几乎完全从边缘服务、从不熔毁源。用 LRU 等淘汰策略使缓存持有当前热集。
第 9 步 — 过期与清理
过期粘贴必须停止被服务并最终被回收。三个互补机制(与 URL 短链同一套):
- 惰性删除——读时若
expires_at已过,把它当没了(404)并可选当时删除它。便宜,但过期但未读的粘贴滞留。 - 定时清扫器——后台作业周期性扫描
expires_at索引并批量删除过期元数据 + 它们的 blob,回收存储。 - 对象存储 TTL——让 blob 存储经生命周期规则自动过期对象,使内容清理由存储层处理。
第 10 步 — 扩展与容错
app/API 层无状态、在负载均衡器后横向扩展。元数据存储按 key 分片并为可用复制;blob 存储(S3)已提供 ~11 个 9 持久性和实际无限的规模。缓存层通过跨 Redis 节点分片(一致性哈希)扩展。因为每层独立扩展且内容层不可变,系统几乎线性增长——主要容量关切是读尖峰,完全由缓存 + CDN 吸收。
第 11 步 — 安全与滥用防护
一个公开"粘贴任何东西"的端点是滥用的猫薄荷,所以这值得显式关注:
- 限流创建端点(按 IP / 按用户)以阻止垃圾和存储耗尽攻击。
- 内容扫描恶意软件、钓鱼和垃圾;尊重下架请求。
- 可见性控制——不公开粘贴依赖不可猜的随机键;真正私有粘贴需要认证和访问检查。
- 大小上限和输入校验以防超大上传。
第 12 步 — 关键取舍
- 随机 vs 顺序键。随机键不可猜(不公开粘贴需要)但需冲突处理;顺序键避免冲突但可枚举。粘贴服务倾向随机。
- 内联 vs blob 存储。内联小粘贴省一次往返;blob 存储让数据库为大内容保持精简。一个大小阈值兼得。
- 缓存/CDN TTL。更长边缘缓存意味更少源命中但更粗控制;由粘贴过期限制它并接受轻微删除延迟。
- 一致性。粘贴不可变且写一次,所以缓存的最终一致无害——新创建的粘贴只需它的首次读命中源。
Pastebin 是两个想法的课:分离小元数据与大内容(DB 存指针、对象存储存字节),并利用不可变性狠狠缓存。一旦那些点通了,其余——key 生成、过期、滥用处理——是标准的。读路径通过缓存 + CDN 的尖峰容忍,正是把一个简单 CRUD 应用变成可扩展服务的东西。
Pastebin 与 URL 短链有何不同?它存大内容,而非一个小目标 URL——所以关键决策是元数据-vs-blob 存储,且缓存重要得多。
粘贴内容住哪?在对象存储(S3),数据库里只有一个小指针 + 元数据行;极小粘贴可能内联以跳过往返。
为什么缓存在这这么有效?粘贴不可变且读多,所以缓存/CDN 条目从不需要失效——完美吸收病毒读尖峰。
随机还是顺序键?随机,因为不公开粘贴依赖不可猜的 URL;顺序 ID 会让任何人枚举所有粘贴。
粘贴怎么过期?TTL 配读时惰性删除加后台清扫器(和对象存储生命周期规则)以回收存储。