本文目录
上级:后端学习 from zero
架构演进的本质,是用空间换时间、用复杂度换性能。性能问题通常不是凭空出现的,而是用户、请求和并发增加后,原来的资源边界被逐渐触碰。
1. 单机
用户很少时,一台机器可以同时运行应用和数据库。部署简单、调用没有网络开销,机器资源也足够使用。
用户增加后,应用和数据库会竞争 CPU、内存、磁盘 I/O 和网络资源。第一步通常是把应用服务器和数据库服务器拆开,让两类负载使用独立资源。
2. 应用集群和负载均衡
单台应用服务器有负载上限,就部署多台相同代码,通过负载均衡分发请求,这叫水平扩展;多台服务器组成集群。
负载均衡还带来高可用能力:某台机器故障后,流量可以转移到正常实例。此时应用尽量设计为无状态,用户状态放在共享存储中,避免请求必须命中某一台机器。
3. 缓存
数据库使用磁盘保存完整数据,而内存读取快得多。对于高频、可接受短暂延迟的数据,可以引入多级缓存:
浏览器 HTTP 缓存
-> 应用本地缓存(Caffeine)
-> 分布式缓存(Redis)
-> 数据库
缓存能拦截大量热点读,但也会引入缓存穿透、击穿、雪崩、更新时机和一致性问题。它不是免费的数据库替代品,应根据数据的可变性和一致性要求选择缓存策略。
4. 读写分离
数据库的读和写工作负载不同,写操作还会产生锁竞争;现实业务往往读多写少,因此写压力可能拖慢读性能。
可以让主库负责写,从库负责读,形成一主多从:
写请求 -> 主库 -> 复制 -> 从库
读请求 ----------------> 从库
读写分离需要处理复制延迟、读己之写、主库故障切换和读路由,不应只把连接配置改成多个地址就认为完成了分离。
5. 分库分表
主从实例仍可能保存同一份单库单表数据,数据量继续增长后,单表和单库会成为逻辑瓶颈。
- 垂直分库:按业务域拆分用户库、商品库、订单库。
- 水平分表:按用户 ID 等分片键把一张大表拆成多张小表。
分片提升存储和并发能力,但跨库事务、跨分片查询、分布式 ID、数据迁移和热点分片都需要额外设计。
6. CDN、网关和中间件
CDN 负责更靠近用户分发静态或可缓存内容;反向代理和网关可以统一处理流量转发、TLS、鉴权、限流和基础安全策略。
业务越来越复杂时,数据库也不应承担所有工作:全文搜索可以交给 Elasticsearch,非结构化或半结构化数据可以使用 NoSQL 或对象存储。
7. 分布式与微服务
单体应用过大后,一个小修改也需要完整部署,团队之间容易互相影响,且无法只给热点业务单独扩容。可以按业务域拆成用户、订单、商品等独立服务,各自部署、发布和扩容。
Client -> Gateway -> User Service
-> Order Service -> Product Service
-> Notification Service
拆分后服务之间不能再依赖本地方法调用,需要网络通信。常见配套设施包括:
- RPC:统一服务间调用协议。
- 服务注册与发现:让服务能找到彼此。
- 消息队列:把同步调用改成异步事件,削弱时间耦合。
- 全链路追踪:定位跨服务慢请求。
- 限流、熔断、降级:避免故障扩散。
- 日志、指标和告警:提供可观测性。
消息队列的价值不只是“更快”,还包括解耦生产者与消费者、削峰填谷、失败重试和最终一致性。但它会带来重复消费、乱序、堆积和投递失败等新问题。
8. 容器与云
服务数量增加后,手工管理运行环境和机器成本很高。容器封装运行环境,Kubernetes 负责调度、自动重启、滚动发布和扩缩容;云平台则让计算、存储和网络资源按需申请、按量付费。
什么时候不要演进
每一步都是 trade-off。小流量项目可能只需要单体 + 数据库 + 合理索引;只有当监控数据证明瓶颈存在,或业务确实需要隔离发布、独立扩容和高可用时,才引入下一层复杂度。按最复杂的分布式 case 设计,通常是在逃避对业务规模和一致性要求的判断。