跳转至

课程一:单体架构

系统架构演进式学习方案

目标:很多复杂的系统都是从一个简单的起点逐步演化而来。本课程将通过一个渐进式场景驱动的方式,模拟一个典型 Web 应用(博客平台)在真实业务增长中可能遇到的架构挑战,带你一步步掌握经典问题的解决思路,像架构师一样思考


阶段 0:初始系统(单体架构,一切的起点)

系统描述

  • 想象一下,我们从一个最简单的博客平台开始,功能基础但够用:
  • 用户注册、登录
  • 发布、编辑、删除博客
  • 查看博客列表和详情
  • 技术栈:选型也是最常见的组合:
  • 前端:HTML + JavaScript(早期可能用 jQuery,后来也许是 React/Vue)
  • 后端:Python Flask 或 Java Spring Boot(经典的单体应用)
  • 数据库:MySQL(单机实例,足够应对初期)

当前架构图

[用户浏览器] → [Web 服务器 (单体应用)] → [MySQL (单机)]
此刻的特点: - 代码都在一个工程里,简单直接。 - 应用直接连接数据库读写,没有中间层。 - 扩展能力?暂时还没考虑。


阶段 1:流量来了 → 数据库先告急

挑战浮现

  • 平台受欢迎是好事,但用户量增长后,问题随之而来:数据库查询变慢,特别是访问量大的博客列表页。
  • 高峰期服务器 CPU 飙升,页面响应时间从几十毫秒恶化到半秒甚至更长。

❓ 架构师的思考时刻:瓶颈在数据库,怎么办?

(性能优化的第一反应通常是什么?加缓存?索引?还是直接读写分离?)

✅ 演进方向:缓存先行,兼顾索引

  1. 引入缓存救急:面对读压力,引入内存缓存(如 Redis)是性价比最高的手段。
    • 缓存热门博客列表,大幅减少数据库查询。
    • 先采用常见的 Cache-Aside 模式:程序先读缓存,缓存没有再查数据库,查到后写回缓存。 (当然,缓存一致性问题需要后续关注。)
  2. 数据库自身优化:也不能忘了根本。
    • 检查 blogs 表的索引,确保 created_at 等用于排序和查询的字段有合适的索引。
  3. 架构微调
    [用户浏览器] → [Web 服务器] → [Redis (缓存)] → [MySQL]
    

阶段 2:用户激增 → 单体 Web 服务器的极限

新的瓶颈

  • 流量持续上涨,峰值 QPS 从几百冲到几千,单台 Web 服务器的 CPU 直接被打满。
  • 用户开始频繁遇到访问超时。

❓ 架构师的思考时刻:单点扛不住了,如何横向扩展?

(加机器是肯定的,但怎么让多台机器协同工作?用户状态怎么处理?)

✅ 演进方向:负载均衡 + 无状态化

  1. 横向扩展 Web 层
    • 增加部署多台 Web 服务器实例。
    • 在前端引入 Nginx 作为负载均衡器,将请求分发到后端的多个实例(常用的策略有轮询、最少连接等)。
  2. 服务无状态化是关键:要实现轻松扩展,Web 服务器自身不能存储用户会话(Session)等状态信息。
    • 将 Session 数据迁移到外部共享存储,如 Redis。这样任何一台 Web 服务器都能处理任意用户的请求。
  3. 架构演变为
    [用户浏览器] → [Nginx (负载均衡)] → [Web 服务器实例 x N] → [Redis (缓存+Session)] → [MySQL]
    

阶段 3:写入压力增大 → 数据库读写分离

写操作成为新焦点

  • 随着用户活跃度提升,博客发布、编辑操作越来越频繁,MySQL 主库的写入压力凸显,甚至导致主从同步延迟增大。
  • 用户开始抱怨:"刚发布的博客,刷新好几次都看不到!"

❓ 架构师的思考时刻:读性能解决了,写瓶颈怎么办?

(主从复制是标准答案吗?分库分表是不是太早了?有没有中间方案?)

✅ 演进方向:实施主从复制与读写分离

  1. 启用 MySQL 主从复制
    • 配置一个主库(Master)处理所有写操作。
    • 配置一个或多个从库(Slave)处理读操作,分摊读压力。
    • 引入 数据库中间件(如 ShardingSphere-JDBC/ProxyProxySQL)来自动路由读写请求,对应用层透明。
  2. 应对主从延迟
    • 读写分离后,需要关注主从延迟问题。对于一致性要求高的读请求(如刚发布后立即查看),可能需要强制路由到主库。
    • 缓存更新策略也需调整,例如写后失效缓存而非更新,降低不一致窗口。
  3. 架构再次调整
    [用户浏览器] → [Nginx] → [Web 服务器 x N] → [Redis]
                                          ↘ [DB 中间件/代理] ↘ [MySQL Master] → [MySQL Slave x N]
    

阶段 4:业务日益复杂 → 单体拆分的抉择:微服务

单体的"成长的烦恼"

  • 业务不断发展,增加了"评论系统"、"用户推荐"等新模块,单体应用的代码库变得越来越庞大,维护困难。
  • 不同功能的开发部署互相影响,团队协作效率下降,发布周期变长。

❓ 架构师的思考时刻:是时候"分家"了,如何优雅地拆分?

(微服务是趋势,但如何划分服务边界?服务间如何通信?异步化怎么引入?)

✅ 演进方向:拥抱微服务,引入消息队列与 API 网关

  1. 按业务能力拆分服务
    • 将单体应用拆分为独立的 博客服务、用户服务、评论服务 等。每个服务可以独立开发、部署和扩展。
    • 服务间通信:初期可选用 REST API,性能要求高或内部调用可考虑 RPC (gRPC/Dubbo)
  2. 引入消息队列实现异步解耦
    • 对于非核心、可异步处理的流程(如"博客发布后通知关注者"、"触发推荐计算"),引入 KafkaRabbitMQ。生产者发送消息,消费者异步处理,提高系统韧性和响应速度。
  3. 构建 API 网关
    • 所有外部请求(来自客户端)统一通过 API 网关(如 Kong, Spring Cloud Gateway, Nginx+Lua)接入。
    • 网关负责:路由、认证鉴权、限流熔断、日志监控等通用功能,简化后端服务。
  4. 演变后的微服务架构雏形
    [用户浏览器] → [API 网关] → [用户服务] → [Redis]
                          ↑ ↓         ↑ ↓
                          [博客服务] → [MySQL]
                          ↑ ↓         ↑ ↓
                          [评论服务] → [Kafka] → [推荐计算服务] → ...
                                        [通知服务] → ...
    

阶段 5:数据量井喷 → 分库分表与搜索引擎

海量数据的挑战

  • 博客内容和用户数据持续爆发式增长,核心的 blogs 表达到 TB 级别,即使做了读写分离,单库或单表的查询性能也急剧下降。
  • 用户对内容搜索的需求越来越强烈,简单的 LIKE 查询已无法满足。

❓ 架构师的思考时刻:数据库容量和查询性能再次告急,怎么办?

(分库分表是必然选择,但按什么维度分?全文搜索用什么技术?)

✅ 演进方向:数据分片 + 引入专业搜索引擎

  1. 实施分库分表
    • 针对数据量最大的表(如博客表、用户表),进行水平拆分。常见的策略是按 用户 ID内容 ID 哈希分片。
    • 引入 数据库分片中间件(如 ShardingSphere, Vitess, MyCat)来管理分片路由规则,对应用层屏蔽底层细节。
  2. 引入 Elasticsearch 实现全文检索
    • 将需要搜索的博客内容(标题、正文等)同步到 Elasticsearch 集群中。
    • 利用 ES 强大的倒排索引和分词能力,提供高效、精准的全文搜索功能。同步机制可以考虑用 CDC (Canal/Debezium) 或双写。
  3. 考虑冷热数据分离
    • 对于访问频率很低的旧博客数据,可以考虑从主存储(MySQL/ES)归档到成本更低的对象存储(如 AWS S3, 阿里云 OSS),降低在线存储压力。
  4. 数据存储层的演变
    主要在线存储: [MySQL (分库分表集群)] + [Elasticsearch (搜索集群)]
    归档存储: [对象存储 (S3/OSS)]
    

阶段 6:全球化征程 → 多活架构的挑战

业务出海的新需求

  • 平台需要服务全球用户,但海外用户访问国内数据中心延迟太高,体验不佳。
  • 对可用性提出更高要求:即使单个数据中心发生故障,服务也不能中断。

❓ 架构师的思考时刻:如何实现全球低延迟访问和跨地域容灾?

(多地部署是必须的,但数据怎么同步?用户请求怎么路由?)

✅ 演进方向:构建多活数据中心 + CDN 加速

  1. 多地域部署(多活架构)
    • 在不同的地理区域(如美东、欧洲、新加坡)部署独立的、功能完整的服务集群。
    • 数据库层面需要支持跨区域复制和一致性的方案,如 全球数据库 (AWS Aurora Global Database, Google Spanner, CockroachDB) 或自建同步方案(可能牺牲强一致性)。
  2. CDN 加速静态资源
    • 将图片、CSS、JavaScript 等静态资源部署到 CDN (Content Delivery Network),利用其全球边缘节点为用户提供就近访问,极大降低延迟。
  3. 全局流量调度
    • 使用 智能 DNS全局负载均衡 (GSLB) 服务,根据用户地理位置、网络延迟或服务健康状况,将用户请求路由到最近或最健康的区域数据中心。
  4. 最终架构形态(示意)
    [北美用户] → [GSLB/智能DNS] → [北美数据中心集群] → [全球数据库/同步链路]
    [亚洲用户] → [GSLB/智能DNS] → [亚洲数据中心集群] → [全球数据库/同步链路]
    [欧洲用户] → [GSLB/智能DNS] → [欧洲数据中心集群] → [全球数据库/同步链路]
    
    [CDN (全球边缘节点)] → [对象存储 (源站)]
    

总结:一条典型的架构演进之路

阶段 核心问题 关键解决方案 代表技术/模式
0. 单体 业务简单 单体应用 + 单机数据库 Flask/Spring Boot, MySQL
1. 缓存 读性能瓶颈 引入缓存 + 数据库索引优化 Redis, Cache-Aside
2. 水平扩展 Web 服务器压力 负载均衡 + 服务无状态化 Nginx, Redis (Session)
3. 读写分离 数据库写瓶颈 主从复制 + 读写分离中间件 MySQL Replication, ProxySQL/ShardingSphere
4. 微服务 单体复杂难维护 服务拆分 + 异步解耦 RPC/REST, Kafka/RabbitMQ, API Gateway
5. 数据扩展 数据量巨大/搜索需求 分库分表 + 搜索引擎 ShardingSphere/Vitess, Elasticsearch
6. 全球化 低延迟/高可用 多活部署 + CDN 全球数据库, GSLB, CDN

学习方法建议

  1. 动手实践至关重要:每个阶段都可以尝试用云服务(AWS/Azure/阿里云等免费套餐或小规格实例)或本地 Docker/K8s 搭建一个最小化的 Demo,亲身体验配置和效果。
  2. 深入对比思考:主动比较同类技术的优劣,例如:"Kafka vs RabbitMQ 各自适合什么场景?"、"Redis Sentinel 和 Cluster 模式有何不同?"。
  3. 模拟故障场景:如果条件允许,可以尝试使用混沌工程工具(如 Chaos Mesh)或手动方式模拟节点宕机、网络延迟等故障,观察系统的反应和恢复能力。

通过模拟这条真实的业务增长和技术演进路径,我们能更好地理解各种架构设计决策背后的权衡与取舍,这正是架构师的核心价值所在。