课程二:分布式缓存
课程二:社交网络(Twitter/微博类)架构演进案例
目标:社交网络是互联网应用中的明珠,其背后涉及诸多有趣的架构挑战。本课程将以一个类似 Twitter/微博 的系统为例,逐步展开其架构演进过程,带你掌握高并发读写下的 Feed 流设计、关系图谱的存储与查询、实时互动推送等经典问题的解决方案。
阶段 0:初始系统(简版微博,够用就好)
系统描述
- 核心功能非常基础:
- 用户注册、登录
- 发布短文本(比如限制 140 字)
- 关注其他用户
- 查看自己关注的人发布的动态(Timeline 或称"信息流")
- 技术栈:依旧是熟悉的简单组合:
- 前端:React/Vue
- 后端:Node.js 或 Spring Boot(单体模式启动)
- 数据库:MySQL(用户表、帖子表、关注关系表,关系型数据库搞定一切)
当前架构图
此刻的特点: - 功能直接,实现简单:用户的 Timeline 是通过posts
表和 follows
表直接 JOIN
查询生成的。
- 没有缓存,没有分页,一切都很"原始"。数据量小的时候,这完全没问题。
阶段 1:用户多了,刷不动 Timeline 了 → Feed 流设计的核心抉择
挑战浮现
- 用户量和帖子量上来后,最核心的 Timeline 查询(
SELECT posts FROM posts JOIN follows WHERE follower_id=X ORDER BY created_at DESC LIMIT N
)变得异常缓慢,数据库压力山大。 - 更糟糕的是,当一个"大V"(拥有众多粉丝的用户)发布新帖子时,大量粉丝同时刷新 Timeline,可能瞬间压垮数据库。
❓ 架构师的思考时刻:Feed 流性能瓶颈,推还是拉?
(这是社交 Feed 设计中最经典的问题!直接查库肯定不行,是发帖时就处理好,还是看的时候再聚合?)
✅ 演进方向:推拉结合,缓存是关键
解决 Feed 流性能问题,主要有两种思路:写扩散(推)和读扩散(拉)。
- 写扩散 (Push 模型 - 主动推送):
- 做法:用户发布新帖时,系统主动将这条帖子的 ID 推送到其所有粉丝的 Timeline 缓存 中(通常使用 Redis Sorted Set,按时间戳排序)。用户读取 Timeline 时,只需从自己的缓存中获取帖子 ID 列表,再去查询帖子内容。
- 优点:读 Timeline 的性能极快,因为直接读缓存。
- 缺点:对于粉丝量巨大的"大V"来说,一次发帖可能触发百万甚至千万次的缓存写入操作(写风暴),对 Redis 和推送系统压力极大。
- 读扩散 (Pull 模型 - 被动拉取):
- 做法:用户访问 Timeline 时,系统实时查询其关注的所有用户的最新帖子,然后进行聚合、排序后返回。
- 优点:发帖操作非常轻量。特别适合粉丝很少的"普通用户"。
- 缺点:读 Timeline 的压力很大,用户关注的人越多,查询越慢。对于大V的粉丝来说,体验可能很差。
- 业界常用:混合模式 (Hybrid):
- 结合推和拉的优点:对普通用户(粉丝数 < 阈值,如 1 万)采用 写扩散,保证其粉丝快速看到更新。
- 对大V用户(粉丝数 > 阈值)采用 读扩散。粉丝读 Timeline 时,需要合并拉取大V的帖子和自己缓存中的普通用户帖子。通常还会对大V的热门帖子做额外缓存。
架构调整(以混合模式为例):
graph LR
subgraph 发帖流程
A["用户发帖"] --> B{"判断是否大V"};
B -- 是 --> C["存储帖子到DB"];
B -- 否 --> D["存储帖子到DB"];
D --> E["获取粉丝列表"];
E --> F["推送帖子ID到粉丝Timeline缓存(Redis ZSet)"];
end
subgraph 读Timeline流程
G["用户请求Timeline"] --> H{"获取关注列表(含大V)"};
H --> I["从Redis读Timeline缓存(普通用户帖子)"];
H --> J["查询关注的大V最近帖子(DB/Cache)"];
I & J --> K["合并排序帖子列表"];
K --> L["查询帖子内容(DB/Cache)"];
L --> M["返回Timeline给用户"];
end
(简化表示,实际架构更复杂,涉及服务拆分等)
核心依赖: [Web Server]
→ [Redis (Timeline Cache)]
& [MySQL (Posts/Follows)]
阶段 2:关系网越来越复杂 → 图数据库登场
新的瓶颈
- 随着用户规模增长,关注关系
follows
表膨胀到数十亿甚至百亿级别。此时,使用 MySQL 进行复杂的关系查询,比如"查询我和 A 的共同关注"、"计算二度人脉推荐关注"等,性能变得极其低下,甚至无法完成。
❓ 架构师的思考时刻:MySQL 处理复杂关系力不从心,怎么办?
(图结构的数据,是不是该用专门的图数据库了?Neo4j 还是 JanusGraph?或者有什么变通方法?)
✅ 演进方向:引入图数据库,优化关系查询
- 迁移核心关系到图数据库:
- 将用户建模为图中的节点 (Node),关注关系建模为边 (Edge)。使用专业的图数据库(如 Neo4j, JanusGraph)来存储和管理这些关系数据。
- 图数据库天然擅长处理多层级的关联查询,如"查找 A 关注的人关注的人中,A 尚未关注的人"(N 度好友推荐),性能远超关系型数据库的递归 JOIN。
- 缓存常用关系:
- 对于热点用户(如明星、KOL)的关注/粉丝关系,可以使用 Redis Graph 或其他内存图缓存方案进行缓存,加速查询。
- 离线计算推荐:
- 更复杂的全局关系分析(如社区发现、大规模好友推荐)可以交给离线计算框架处理,例如使用 Spark GraphX 分析全量关系图谱,预先计算好推荐结果存入缓存或 KV 存储。
架构调整:
数据存储层演变:
[MySQL] → 存储用户基础信息、帖子内容等非关系密集型数据
[Neo4j / JanusGraph] → 存储关注关系、好友关系等图结构数据
[Redis Graph / KV Store] → 缓存热点关系、预计算的推荐结果
阶段 3:点赞评论要实时 → 消息队列与 WebSocket
实时互动的需求
- 用户期望能实时收到"有人点赞了你的帖子"、"有人评论了你"之类的通知,而不是需要手动刷新才能看到。
- 如果客户端采用传统的定时轮询 API 的方式来检查通知,会对服务器造成巨大的、不必要的压力。
❓ 架构师的思考时刻:如何将消息实时推送到在线用户?
(轮询肯定不行。是 WebSocket?还是 SSE?消息量大了怎么处理?)
✅ 演进方向:WebSocket 长连接 + 消息队列解耦
- 建立 WebSocket 长连接:
- 客户端(App 或 Web)与服务器建立 WebSocket 长连接。WebSocket 提供全双工通信,允许服务器主动向客户端推送消息。
- 引入消息队列 (Kafka) 解耦:
- 当发生点赞、评论、@ 等事件时,相应的服务(如帖子服务、评论服务)不直接调用推送逻辑,而是将通知事件发送到 Kafka 消息队列中。
- 独立的 推送服务 (Push Service) 消费 Kafka 中的消息,查询需要通知的用户及其在线状态。
- 推送给在线用户:
- 如果用户在线(即有活跃的 WebSocket 连接),推送服务通过对应的 WebSocket 连接将通知实时推送给用户。
- 离线消息存储:
- 如果用户不在线,可以将通知(或通知计数)存储到 MongoDB 或其他适合存储非结构化数据的 NoSQL 数据库中。用户下次登录时,客户端主动拉取离线期间的通知。
架构调整:
graph TD
A["用户操作(点赞/评论)"] --> B("业务服务");
B --> C("发送事件到 Kafka");
C --> D["推送服务 (消费Kafka)"];
D --> E{"用户是否在线?"};
E -- 在线 --> F("通过 WebSocket 推送");
F --> G["客户端接收实时通知"];
E -- 离线 --> H["存储离线通知到 MongoDB"];
I["用户登录"] --> J("客户端拉取离线通知");
J --> H;
G <-.-> K("WebSocket 连接");
I <-.-> K;
阶段 4:热点事件来了 → 限流、降级保平安
突发流量的冲击
- 遇到社会热点事件(如明星八卦、重大新闻),平台流量可能瞬间暴涨几十甚至上百倍,远超平时负载。
- 如果没有应对措施,很可能导致整个系统响应缓慢甚至服务雪崩。
- 在这种极端情况下,需要优先保障核心功能(如发帖、浏览 Timeline)的可用性。
❓ 架构师的思考时刻:如何优雅地应对流量洪峰,防止系统被打垮?
(不能硬抗,得有取舍。限流是必须的,哪些功能可以降级?)
✅ 演进方向:多层限流 + 服务降级 + 弹性伸缩
- 入口层限流 (Rate Limiting):
- 在 API 网关(如 Nginx, Kong)层配置限流策略,对非核心 API(如"热搜榜"、"附近的人"等)进行 QPS 限制,甚至暂时关闭。优先保证核心 API 的流量。
- 服务降级策略:
- 识别并区分核心功能和非核心功能。当系统负载过高时,主动关闭或简化非核心功能。
- 例如:暂时关闭"个性化推荐"模块,所有用户返回统一的静态热门内容缓存;暂时关闭"实时在线状态"显示。
- 后端服务熔断:
- 服务间的调用需要有熔断机制(如使用 Hystrix, Sentinel,或 Istio 的熔断能力),当某个下游服务故障或响应过慢时,快速失败,避免请求堆积和雪崩。
- 弹性扩容是基础:
- 对于无状态的服务(如 Web 服务器、API 网关),部署在 Kubernetes 上,并配置 HPA (Horizontal Pod Autoscaler),使其能够根据 CPU、内存或 QPS 等指标自动扩容实例数以应对增长的流量。
架构调整(关注点):
阶段 5:走向世界 → 全球化部署与数据同步
业务全球化需求
- 为了服务全球用户并提供更好的访问体验,需要在世界多个地理区域部署服务节点。
- 随之而来的是挑战:如何降低海外用户的访问延迟?如何保证分布在不同地域的数据的一致性(比如用户在中国关注了一个人,在美国能立刻看到吗?)?
❓ 架构师的思考时刻:多活部署是方向,但数据一致性与延迟如何平衡?
(跨国网络延迟是物理限制。是追求强一致还是最终一致?用户数据放哪里?)
✅ 演进方向:多活数据中心 + 全球数据库/最终一致性 + CDN
- 多活数据中心部署:
- 在关键目标区域(如北美、欧洲、亚洲)设立独立的数据中心或使用云厂商的多 Region 能力,部署完整的服务。
- 数据库全球化方案:
- 方案一:全球数据库。使用 AWS Aurora Global Database, Google Spanner, CockroachDB 等原生支持跨区域读写和低延迟同步的分布式数据库。通常能提供较强的一致性保证,但成本较高。
- 方案二:基于最终一致性的同步。核心用户数据(如账号信息)可能需要强一致同步,但像帖子、关注关系等可以接受最终一致性。写操作发生在用户所在 Region 的主库,然后通过消息队列或其他同步机制异步复制到其他 Region。需要处理冲突和保证数据收敛。
- CDN 全球加速:
- 图片、视频、用户头像等静态资源必须通过 CDN 进行全球分发,确保用户从最近的边缘节点加载。
- 智能路由:
- 采用 GSLB (Global Server Load Balancing) 或智能 DNS,根据用户的地理位置、网络状况将请求导向最近或最健康的 Region。
架构调整(示意):
graph TD
subgraph 用户访问
U_AS["亚洲用户"] --> R_AS("亚洲 Region GSLB/DNS");
U_US["北美用户"] --> R_US("北美 Region GSLB/DNS");
R_AS --> C_AS("亚洲服务集群");
R_US --> C_US("北美服务集群");
end
subgraph 数据层
C_AS --> DB_AS["亚洲数据中心 (主写或分区)"];
C_US --> DB_US["北美数据中心 (主写或分区)"];
DB_AS <-->|"全球数据库/同步机制"| DB_US
end
subgraph 静态资源
S3["对象存储源站"] --> CDN("全球CDN边缘节点");
U_AS --> CDN;
U_US --> CDN;
end
总结:社交网络架构的演进之路
阶段 | 核心挑战 | 关键解决方案 | 代表技术/模式 |
---|---|---|---|
0. 简版 | 功能实现 | 单体 + 关系型数据库 | MySQL JOIN |
1. Feed 流 | Timeline 性能 | 推拉结合 Feed 流 + 缓存 | Redis Sorted Set, 写扩散/读扩散/混合模式 |
2. 关系图谱 | 复杂关系查询慢 | 引入图数据库 | Neo4j / JanusGraph, 图存储 |
3. 实时互动 | 消息推送延迟/开销 | WebSocket + 消息队列解耦 | Kafka/RabbitMQ, WebSocket 长连接, 离线消息存储 |
4. 流量冲击 | 服务易雪崩 | 入口限流 + 服务降级 + 弹性伸缩 | Nginx/API Gateway 限流, K8s HPA, 熔断 |
5. 全球化 | 跨地域延迟/一致性 | 多活数据中心 + CDN + 全球同步 | GSLB, 全球数据库 / 最终一致性同步, CDN |
课程设计思路与启发
- 问题驱动,场景说话:每个演进阶段都源于一个真实的业务痛点(如 Timeline 刷不动、大V效应、实时通知需求),这比单纯罗列技术更能激发思考。
- 权衡的艺术:架构设计充满了取舍。课程中对比了推拉模式、不同数据库选型、同步异步通信等,引导学习者理解"没有银弹",只有最适合当前场景的选择。
- 技术的关联性:展示了缓存、消息队列、图数据库、WebSocket 等技术是如何组合起来解决复杂问题的。
- 从简入繁:遵循系统发展的自然规律,从简单的单体逐步引入分布式、异步化、实时化等复杂技术,更易于理解和接受。
理解社交网络的架构演进,能帮助我们掌握构建大规模、高并发、实时互动系统的核心设计原则和常用技术手段。