system-design-notes第23章:设计分布式邮件服务(类Gmail)完整指南
【免费下载链接】system-design-notesNotes of the book System Desgin Interview - An Insider's Guide项目地址: https://gitcode.com/GitHub_Trending/sy/system-design-notes
本文基于system-design-notes第23章,带你完整走一遍如何设计一个类似Gmail的分布式邮件服务:从容量估算、高层架构、邮件收发流程,到元数据数据库与全文搜索,帮助新手快速掌握这个经典的系统设计面试题。
📌 为什么设计"类Gmail"邮件系统
2020年 Gmail 拥有18亿活跃用户,Outlook 也有4亿用户,邮件服务是典型的大规模分布式系统设计问题。
核心功能(设计范围):
- 📤 发送邮件 / 📥 接收邮件
- 📂 获取与过滤邮件
- 🔍 全文搜索
- 🛡️ 反垃圾邮件保护
非功能性需求:
| 需求 | 关键点 |
|---|---|
| 可靠性 | 绝不能丢失用户的邮件 |
| 可用性 | 用复制防止单点故障,容忍部分失败 |
| 可扩展性 | 用户增长时系统能弹性扩展 |
| 灵活性 | 选用 HTTP 而非 SMTP 等协议,便于扩展新功能 |
🎯 第1步:明确需求与容量估算
开始设计前,先做背级估算(back-of-the-envelope estimation):
| 指标 | 假设 | 估算结果 |
|---|---|---|
| 用户规模 | 10亿用户 | — |
| 发送负载 | 每人每天发 10 封 | 约10万封/秒 |
| 元数据存储 | 每人每天收 40 封,每封约 50KB 元数据 | 约730PB/年 |
| 附件 | 20% 的邮件带 500KB 附件 | 约1460PB/年 |
💡 存储量每年超过 2PB,这正是"必须分布式"的根本原因。估算方法可参考 02. Back Of the Envelope Estimation。
🏗️ 第2步:高层架构设计
从传统邮件服务器说起
先看传统邮件服务器如何工作:Alice 通过SMTP把邮件发到 Outlook 服务器,Outlook 服务器查询DNS 的 MX 记录找到 Gmail 的邮件服务器并转发,Bob 再用IMAP/POP从本地服务器取信。

跨服务器发邮件前需先查询 MX 记录,以下是查询 gmail.com 的 MX 记录示例:

传统架构中,每封邮件都是本地文件系统里的一个独立文件:

随着规模增长,磁盘 I/O 成为瓶颈,且磁盘损坏、服务器宕机无法满足高可用与可靠性要求。
分布式邮件服务器架构
分布式邮件服务器仍支持 IMAP/POP 与 SMTP,但对 Web 端则提供HTTP 上的 RESTful API:
POST /v1/messages— 发送邮件(To、Cc、Bcc)GET /v1/folders— 获取全部文件夹GET /v1/folders/{:folder_id}/messages— 分页获取文件夹中的邮件GET /v1/messages/{:message_id}— 获取邮件详情
高层架构包含 7 个部分:

| 组件 | 职责 |
|---|---|
| Webmail | 用户通过浏览器收发邮件 |
| Web Servers | 登录、注册、用户画像等对外服务 |
| Real-time Servers | 实时推送新邮件(WebSocket,旧浏览器降级长轮询) |
| Metadata DB | 主题、正文、发件人等元数据 |
| Attachment Store | 对象存储(类 S3),存大文件 |
| Distributed Cache | 用 Redis 缓存最近邮件,改善体验 |
| Search Store | 分布式文档库,支持全文搜索 |
设计原文见 Step 2: High-Level Design。
💌 邮件发送流程详解

流程拆解:
- 用户写完邮件点"发送",请求先到负载均衡器
- 负载均衡器做限流,再路由到某台 Web 服务器
- Web 服务器做基础校验(如邮件大小);发件人与收件人同域则短路直发(先做垃圾检查)
- 校验通过 → 写入发送消息队列(附件只引用对象存储的地址);失败 → 进错误队列
- SMTP 出站工作器拉取消息,执行垃圾邮件/病毒检查后路由到目标邮件服务器
- 邮件存入"已发送"文件夹
⚠️ 务必监控发送队列长度:接收方服务器不可用时用指数退避重试;消费者吞吐不足时扩容消费者。
📥 邮件接收流程详解

- 入站邮件先到SMTP 负载均衡器
- SMTP 服务器执行邮件接收策略(无效邮件直接丢弃)
- 附件过大时,转存对象存储(S3)
- 邮件处理工作器做初步检查(垃圾、病毒)后,把数据写入存储层:元数据库、搜索库、对象存储、缓存
- 在线用户通过 WebSocket 实时推送收信;离线用户上线后走 HTTP API 拉取
💾 第3步:元数据数据库设计
邮件元数据的特征:
- 头部小、访问频繁;正文可大可小,但通常只读一次
- 大多数操作孤立于单个用户(取信、标已读、搜索)
- 用户通常只读最近的邮件
- 数据丢失不可接受,可靠性要求最高
在 Gmail 这个量级,数据库通常自研以降低 IOPS。现有方案都有短板:
| 选项 | 短板 |
|---|---|
| 关系型数据库 | 为小数据块优化,不适合大正文列 |
| 分布式对象存储 | 适合备份,但搜索/标记已读效率低 |
| NoSQL(如 BigTable) | Gmail 所用方案,未开源 |
关键设计点:
- 用
user_id作为分区键,一个用户的数据落在同一分片 - 主键 = 分区键(数据分布)+ 聚类键(数据排序)
email_id使用TimeUUID,天然按创建时间排序
核心表结构如下(K = 分区键,C↓ = 聚类键降序):

附件单独建表,以文件名为键,正文中只存对象存储的引用:

已读/未读过滤在 NoSQL 中是痛点(禁止在非键字段上过滤),解法是把邮件表反范式化为已读/未读两张表:

一致性权衡上用可用性换一致性(邮件不能丢是硬性要求),故障切换或网络分区时,同步操作会短暂不可用。
🔍 邮件全文搜索怎么做
与 Google 搜索不同,邮件搜索是用户本地的:
| Google 搜索 | 邮件搜索 | |
|---|---|---|
| 范围 | 整个互联网 | 用户自己的邮箱 |
| 排序 | 按相关性 | 按时间、日期等属性 |
| 准确性 | 建索引耗时,结果非实时 | 必须快且结果准确 |
邮件搜索写多读少(每次收/发/删都要重建索引),但用户很少用搜索功能。两种方案:
方案 A:Elasticsearch 集群(推荐)

- 变更操作(发/收/删)经Kafka 异步触发重建索引,与服务解耦
- 实际搜索查询是同步的
- 同样用
user_id做分区键,把同一用户的数据分到同一节点
方案 B:自研搜索引擎
- 用 **LSM 树(Log-Structured Merge-Tree)**组织磁盘索引,写路径只做顺序写优化——Cassandra、BigTable、RocksDB 都采用这一技术:数据先在内存攒批,达到阈值后逐层合并到磁盘

两方案取舍:
| 维度 | Elasticsearch | 自研搜索引擎 |
|---|---|---|
| 扩展性 | 够用 | 可为邮件场景精调,扩展上限更高 |
| 维护成本 | 独立服务需单独维护 | 可与存储引擎合一 |
| 开发成本 | 现成方案 | 需大量工程量 |
🌍 投递性、扩展性与高可用
**邮件投递性(deliverability)**是个"隐形"难题:新搭的邮件服务器直接发信,大概率进垃圾箱。常用手段:
- 🆔 使用专用 IP建立信誉
- 🔥预热 IP(2~6 周)积累良好声誉
- 🏷️分类邮件——营销邮件不与重要邮件共用发送服务器
- 🛡️ 用SPF、DKIM等认证技术反钓鱼
- 🚫 快速封禁垃圾用户,并与 ISP 建立反馈循环跟踪投诉率
扩展性:单个用户的操作互不干扰,多数组件可独立水平扩展。
高可用:多数据中心部署 + 主从故障切换:

📝 总结与延伸思考
本章分布式邮件服务设计可归纳为 5 点:
- 架构:Web / Real-time 双服务器 + 元数据、附件、缓存、搜索四层存储
- 异步解耦:收发流程走消息队列,并监控队列做退避与扩容
- 存储:
user_id分区、TimeUUID 排序、已读/未读反范式化、一致性优先 - 搜索:Elasticsearch 集群 + Kafka 异步建索引
- 可用:多数据中心复制 + 故障切换
面试中还可延伸:容错(节点故障如何处理)、合规(GDPR 下个人数据存储)、安全(邮件加密、反钓鱼)、优化(不同用户重复发送的相同附件去重)。
📚 本章资料:23. Distributed Email Service/README.md;附件对象存储与 24. S3-like Object Storage 的设计思路相通,建议对照阅读。
【免费下载链接】system-design-notesNotes of the book System Desgin Interview - An Insider's Guide项目地址: https://gitcode.com/GitHub_Trending/sy/system-design-notes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考