Pastebin 让用户粘贴一块文本并拿回一个任何人都能打开阅读的短 URL。它是 URL 短链的近亲,有一个重要区别:不存一个小目标 URL,你存一个潜在很大的内容 blob。这把设计推向字节住哪的问题,且——因为粘贴被读得远多于被写——推向激进缓存。它是个很好的"中等"面试题,奖励元数据与内容的干净分离。

⚡ 速览要点
  • 每个粘贴生成一个短 base-62 key(随机或从计数器),就像 URL 短链;仅在随机时检查冲突。
  • 把内容存在 blob 存储,而非 DB 行——保留一个小元数据记录(key → blob 位置、过期、标志),把(可能大的)文本放进对象存储。
  • 读多约 10:1+——在 Redis 缓存热粘贴并经 CDN 服务;大多数读绝不该碰数据库或 blob 存储。
  • 粘贴写一次、不可变——这使它们平凡可缓存(无失效)且对 CDN 友好。
  • 过期是一等功能——支持 TTL;用读时惰性删除和后台清扫器的组合清除。
  • 滥用是真正的运维风险——对创建限流、扫描恶意软件/垃圾,并支持不公开/私有粘贴。
tldr

每个粘贴铸造一个短 base-62 key。在数据库保留一个小元数据行(key、blob 指针、过期、可见性),把实际文本存在对象存储(S3)——只对极小粘贴内联。因为粘贴不可变且读多,在 Redis 缓存热的并用 CDN 罩住一切。经 TTL 配惰性 + 定时清理过期。在写路径限流和扫描以对抗滥用。

high-level architecture
                         ┌──────────────┐   ┌───────────────┐
              写/读       │  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 设计

core 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 的强项。而是拆分:

优化

极小粘贴(几百字节),到 blob 存储的往返可能比读本身成本更高。一个常见改进是把小内容直接内联在元数据行,只在超过一个大小阈值时才溢出到 blob 存储——两全其美。

第 6 步 — 数据模型

metadata schema
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 短链同一套):

第 10 步 — 扩展与容错

app/API 层无状态、在负载均衡器后横向扩展。元数据存储按 key 分片并为可用复制;blob 存储(S3)已提供 ~11 个 9 持久性和实际无限的规模。缓存层通过跨 Redis 节点分片(一致性哈希)扩展。因为每层独立扩展且内容层不可变,系统几乎线性增长——主要容量关切是读尖峰,完全由缓存 + CDN 吸收。

第 11 步 — 安全与滥用防护

一个公开"粘贴任何东西"的端点是滥用的猫薄荷,所以这值得显式关注:

第 12 步 — 关键取舍

总结

Pastebin 是两个想法的课:分离小元数据与大内容(DB 存指针、对象存储存字节),并利用不可变性狠狠缓存。一旦那些点通了,其余——key 生成、过期、滥用处理——是标准的。读路径通过缓存 + CDN 的尖峰容忍,正是把一个简单 CRUD 应用变成可扩展服务的东西。

🎯 面试速答

Pastebin 与 URL 短链有何不同?它存大内容,而非一个小目标 URL——所以关键决策是元数据-vs-blob 存储,且缓存重要得多。
粘贴内容住哪?在对象存储(S3),数据库里只有一个小指针 + 元数据行;极小粘贴可能内联以跳过往返。
为什么缓存在这这么有效?粘贴不可变且读多,所以缓存/CDN 条目从不需要失效——完美吸收病毒读尖峰。
随机还是顺序键?随机,因为不公开粘贴依赖不可猜的 URL;顺序 ID 会让任何人枚举所有粘贴。
粘贴怎么过期?TTL 配读时惰性删除加后台清扫器(和对象存储生命周期规则)以回收存储。

← 上一篇
设计分布式键值存储