跳转至

课程二:分布式缓存

课程二:社交网络(Twitter/微博类)架构演进案例

目标:社交网络是互联网应用中的明珠,其背后涉及诸多有趣的架构挑战。本课程将以一个类似 Twitter/微博 的系统为例,逐步展开其架构演进过程,带你掌握高并发读写下的 Feed 流设计、关系图谱的存储与查询、实时互动推送等经典问题的解决方案。


阶段 0:初始系统(简版微博,够用就好)

系统描述

  • 核心功能非常基础:
  • 用户注册、登录
  • 发布短文本(比如限制 140 字)
  • 关注其他用户
  • 查看自己关注的人发布的动态(Timeline 或称"信息流")
  • 技术栈:依旧是熟悉的简单组合:
  • 前端:React/Vue
  • 后端:Node.js 或 Spring Boot(单体模式启动)
  • 数据库:MySQL(用户表、帖子表、关注关系表,关系型数据库搞定一切)

当前架构图

[用户 App/Web] → [Web 服务器 (单体)] → [MySQL (users, posts, follows 表)]
此刻的特点: - 功能直接,实现简单:用户的 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 流性能问题,主要有两种思路:写扩散(推)和读扩散(拉)。

  1. 写扩散 (Push 模型 - 主动推送)
    • 做法:用户发布新帖时,系统主动将这条帖子的 ID 推送到其所有粉丝的 Timeline 缓存 中(通常使用 Redis Sorted Set,按时间戳排序)。用户读取 Timeline 时,只需从自己的缓存中获取帖子 ID 列表,再去查询帖子内容。
    • 优点:读 Timeline 的性能极快,因为直接读缓存。
    • 缺点:对于粉丝量巨大的"大V"来说,一次发帖可能触发百万甚至千万次的缓存写入操作(写风暴),对 Redis 和推送系统压力极大。
  2. 读扩散 (Pull 模型 - 被动拉取)
    • 做法:用户访问 Timeline 时,系统实时查询其关注的所有用户的最新帖子,然后进行聚合、排序后返回。
    • 优点:发帖操作非常轻量。特别适合粉丝很少的"普通用户"。
    • 缺点:读 Timeline 的压力很大,用户关注的人越多,查询越慢。对于大V的粉丝来说,体验可能很差。
  3. 业界常用:混合模式 (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?或者有什么变通方法?)

✅ 演进方向:引入图数据库,优化关系查询

  1. 迁移核心关系到图数据库
    • 将用户建模为图中的节点 (Node),关注关系建模为边 (Edge)。使用专业的图数据库(如 Neo4j, JanusGraph)来存储和管理这些关系数据。
    • 图数据库天然擅长处理多层级的关联查询,如"查找 A 关注的人关注的人中,A 尚未关注的人"(N 度好友推荐),性能远超关系型数据库的递归 JOIN。
  2. 缓存常用关系
    • 对于热点用户(如明星、KOL)的关注/粉丝关系,可以使用 Redis Graph 或其他内存图缓存方案进行缓存,加速查询。
  3. 离线计算推荐
    • 更复杂的全局关系分析(如社区发现、大规模好友推荐)可以交给离线计算框架处理,例如使用 Spark GraphX 分析全量关系图谱,预先计算好推荐结果存入缓存或 KV 存储。

架构调整

数据存储层演变:
[MySQL] → 存储用户基础信息、帖子内容等非关系密集型数据
[Neo4j / JanusGraph] → 存储关注关系、好友关系等图结构数据
[Redis Graph / KV Store] → 缓存热点关系、预计算的推荐结果

阶段 3:点赞评论要实时 → 消息队列与 WebSocket

实时互动的需求

  • 用户期望能实时收到"有人点赞了你的帖子"、"有人评论了你"之类的通知,而不是需要手动刷新才能看到。
  • 如果客户端采用传统的定时轮询 API 的方式来检查通知,会对服务器造成巨大的、不必要的压力。

❓ 架构师的思考时刻:如何将消息实时推送到在线用户?

(轮询肯定不行。是 WebSocket?还是 SSE?消息量大了怎么处理?)

✅ 演进方向:WebSocket 长连接 + 消息队列解耦

  1. 建立 WebSocket 长连接
    • 客户端(App 或 Web)与服务器建立 WebSocket 长连接。WebSocket 提供全双工通信,允许服务器主动向客户端推送消息。
  2. 引入消息队列 (Kafka) 解耦
    • 当发生点赞、评论、@ 等事件时,相应的服务(如帖子服务、评论服务)不直接调用推送逻辑,而是将通知事件发送到 Kafka 消息队列中。
    • 独立的 推送服务 (Push Service) 消费 Kafka 中的消息,查询需要通知的用户及其在线状态。
  3. 推送给在线用户
    • 如果用户在线(即有活跃的 WebSocket 连接),推送服务通过对应的 WebSocket 连接将通知实时推送给用户。
  4. 离线消息存储
    • 如果用户不在线,可以将通知(或通知计数)存储到 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)的可用性。

❓ 架构师的思考时刻:如何优雅地应对流量洪峰,防止系统被打垮?

(不能硬抗,得有取舍。限流是必须的,哪些功能可以降级?)

✅ 演进方向:多层限流 + 服务降级 + 弹性伸缩

  1. 入口层限流 (Rate Limiting)
    • API 网关(如 Nginx, Kong)层配置限流策略,对非核心 API(如"热搜榜"、"附近的人"等)进行 QPS 限制,甚至暂时关闭。优先保证核心 API 的流量。
  2. 服务降级策略
    • 识别并区分核心功能和非核心功能。当系统负载过高时,主动关闭或简化非核心功能。
    • 例如:暂时关闭"个性化推荐"模块,所有用户返回统一的静态热门内容缓存;暂时关闭"实时在线状态"显示。
  3. 后端服务熔断
    • 服务间的调用需要有熔断机制(如使用 Hystrix, Sentinel,或 Istio 的熔断能力),当某个下游服务故障或响应过慢时,快速失败,避免请求堆积和雪崩。
  4. 弹性扩容是基础
    • 对于无状态的服务(如 Web 服务器、API 网关),部署在 Kubernetes 上,并配置 HPA (Horizontal Pod Autoscaler),使其能够根据 CPU、内存或 QPS 等指标自动扩容实例数以应对增长的流量。

架构调整(关注点)

[用户] → [CDN/WAF (基础防护)] → [API 网关 (限流)] → [核心服务集群 (K8s HPA)]
                                              ↘ [非核心服务 (可能降级/限流)]
                                              ↘ [静态缓存/降级数据源]

阶段 5:走向世界 → 全球化部署与数据同步

业务全球化需求

  • 为了服务全球用户并提供更好的访问体验,需要在世界多个地理区域部署服务节点。
  • 随之而来的是挑战:如何降低海外用户的访问延迟?如何保证分布在不同地域的数据的一致性(比如用户在中国关注了一个人,在美国能立刻看到吗?)?

❓ 架构师的思考时刻:多活部署是方向,但数据一致性与延迟如何平衡?

(跨国网络延迟是物理限制。是追求强一致还是最终一致?用户数据放哪里?)

✅ 演进方向:多活数据中心 + 全球数据库/最终一致性 + CDN

  1. 多活数据中心部署
    • 在关键目标区域(如北美、欧洲、亚洲)设立独立的数据中心或使用云厂商的多 Region 能力,部署完整的服务。
  2. 数据库全球化方案
    • 方案一:全球数据库。使用 AWS Aurora Global Database, Google Spanner, CockroachDB 等原生支持跨区域读写和低延迟同步的分布式数据库。通常能提供较强的一致性保证,但成本较高。
    • 方案二:基于最终一致性的同步。核心用户数据(如账号信息)可能需要强一致同步,但像帖子、关注关系等可以接受最终一致性。写操作发生在用户所在 Region 的主库,然后通过消息队列或其他同步机制异步复制到其他 Region。需要处理冲突和保证数据收敛。
  3. CDN 全球加速
    • 图片、视频、用户头像等静态资源必须通过 CDN 进行全球分发,确保用户从最近的边缘节点加载。
  4. 智能路由
    • 采用 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

课程设计思路与启发

  1. 问题驱动,场景说话:每个演进阶段都源于一个真实的业务痛点(如 Timeline 刷不动、大V效应、实时通知需求),这比单纯罗列技术更能激发思考。
  2. 权衡的艺术:架构设计充满了取舍。课程中对比了推拉模式、不同数据库选型、同步异步通信等,引导学习者理解"没有银弹",只有最适合当前场景的选择。
  3. 技术的关联性:展示了缓存、消息队列、图数据库、WebSocket 等技术是如何组合起来解决复杂问题的。
  4. 从简入繁:遵循系统发展的自然规律,从简单的单体逐步引入分布式、异步化、实时化等复杂技术,更易于理解和接受。

理解社交网络的架构演进,能帮助我们掌握构建大规模、高并发、实时互动系统的核心设计原则和常用技术手段。