Skip to content

第 14 讲|企业架构形态

公司里的系统不会都长一个样——内部小工具可能就是一台机器加一个数据库,核心交易系统则涉及多机房、灰度发布、审计、容灾、专门的发布流程。架构形态由业务量、可用性要求、团队规模、历史包袱共同决定,不存在一种通用的"标准架构"。

单机、主备、集群、读写分离、分库分表、缓存、消息队列、微服务、容器化——这些形态是在不同压力阶段被逐步引入的。搞清楚每种形态各自解决什么问题、什么时候该升级到下一阶段,比直接套用一套"高大上架构"更重要

本讲按业务规模递进,从单机到大型分布式架构,讲清楚每一种形态的适用场景、关键能力、典型陷阱。

一、单机形态

最简单的形态——入口、应用、数据库、文件全部在一台或少量几台服务器上:

企业架构从单机主备集群逐步演进

单机形态的合理场景

  • 内部小工具、运维平台
  • 临时搭建的环境
  • 项目早期阶段
  • 测试 / 演示环境
  • 个人项目、博客
  • 边缘节点(IoT、门店本地)

单机不是"落后",而是合理简单。在适合的场景下,单机的运维成本、调试便利性、链路清晰度都远超复杂架构。

单机最大的优势是路径短

  • 所有日志在一台机器上 → tail -f /var/log/* 一处搞定
  • 配置在一处 → 修改不需要分发
  • 备份和恢复流程简单 → 一份镜像 + 一份数据库就完整
  • 调试方便 → ssh 进去什么都能看

单机要补的不是"加机器",是底线能力

单机模式真正需要的不是"扩展",而是底线能力:

能力解决什么
监控告警服务存活、磁盘、内存、CPU 告警
备份数据库定时备份 + 异地存储
重建步骤文档机器挂了后如何快速重建一台
服务自启动systemd 配置,机器重启服务自动起来
日志轮转logrotate 防止日志撑爆磁盘
关键配置版本管理配置进 Git,丢了能恢复

单机不丢人,单机没备份才丢人——能用简单方案解决的问题,不要用复杂方案。

单机的天花板

单机形态的限制:

  • 单点故障 — 这台机器挂,全停
  • 资源上限 — 单机配置有物理上限
  • 发布影响 — 发布时全部用户都跟着停
  • 故障域大 — 一个组件出问题影响全局

业务真正增长到需要 99.9% 可用性、流量增长到单机扛不住时,才考虑升级到下一阶段。

二、主备形态

主备(Active-Standby)给关键组件准备一个接管节点——主节点正常服务,备节点同步数据或待命:

常见的主备组件

对象主备实现方式
数据库主库写入,从库同步,故障时提升从库为主
入口负载均衡Keepalived + VIP 漂移
文件服务主备存储 + 同步复制,或共享存储
Redis主从 + Sentinel(哨兵)
共享存储(NAS/Ceph)主备节点共享后端存储

主备不只是"多装一台机器"

真正可用的主备方案需要一整套配套:

配套能力作用
心跳检测持续监控主节点状态
切换脚本自动化执行切换流程
旧主隔离防止脑裂(下面详述)
客户端连接刷新应用连接池主动重连新主
切换后验证写入测试、关键路径测试
切换演练定期手工触发切换,验证流程可用

任何一环缺失,真故障时切换都做不下去。

数据库主备切换的关键:旧主处理

数据库主备切换里最容易漏的是旧主处理。如果旧主只是网络短暂断开,网络恢复后它还以为自己是主,继续接收写请求,而新主也在接收写请求——脑裂(双写),数据冲突。

切换流程必须明确决定:

旧主处理方式适用场景
关停确认无法恢复 / 严重故障
设为只读旧主可能恢复,但禁止写入
网络隔离(Fencing)通过 iptables 或物理隔离阻止旧主写入
重新作为从库接回旧主硬件正常,数据用复制追平

不同选择对应不同的恢复路径,必须提前想清楚,出事时不能再讨论

三、集群形态

集群用多个节点共同提供服务。最容易理解的是 Web 集群——多个无状态应用实例挂在负载均衡后面:

无状态应用最适合集群化

无状态(Stateless)的关键特征:

  • 实例不保存关键状态
  • 任意请求打到任意实例都能处理
  • 状态从数据库、缓存、对象存储取
  • 挂一个少一个,加一个多一个,扩缩容自由

无状态应用的集群能力是云原生时代所有弹性能力(K8s 自动扩缩、滚动更新、健康检查替换)的基础。

有状态组件的集群:不能一概而论

有状态组件的集群形态因组件而异,不能笼统说"做了集群就高可用了":

组件集群形态
MySQL主从复制、组复制(MGR)、Galera、分片(Vitess)
PostgreSQL流复制、Patroni、Citus
Redis主从 + Sentinel、Redis Cluster(分片)
MongoDBReplica Set、Sharded Cluster
Kafka多 broker + 分区副本
Elasticsearch分片 + 副本
etcdRaft 一致性

每种集群形态都要单独理解:数据归属、复制方式、故障转移机制、扩缩容步骤。MySQL 主从集群、MGR 集群、TiDB 集群,完全不是一回事——运维方法、故障处理、性能特点都不同。

四、读写分离

读写分离把写请求交给主库,读请求分摊到从库,适合"读多写少"场景:

读写分离要处理复制延迟和请求路由

读写分离的核心问题:复制延迟

主从复制是异步或半同步的——主库写入后,从库需要时间同步。这段时间内访问从库会读到旧数据。

最经典的体验问题:写后立即读

用户:修改头像(写主库,成功)
0.5 秒后:刷新个人页(读请求路由到延迟从库)
从库还未同步:返回旧头像
用户:"我刚改的怎么没生效?"

不同请求类型的路由策略

场景路由策略
写操作强制走主库
写后立即读(同一用户的相关读)短时间内强制读主库
普通列表查询、详情查询走从库分摊压力
重型报表查询走专门的只读库或离线数仓
复制延迟过大时暂停读从库,等复制追上
强一致要求(支付、扣款)全部走主库

读写分离的工程要求

接入读写分离需要同步设计:

能力作用
SQL 路由层按 SQL 类型(写/读)和场景选择主从
连接池管理主从分别维护连接池
复制延迟监控持续监控 Seconds_Behind_Master
自动降级延迟过大时自动切回主库
故障切换流程主库故障时的提升流程

"把一个从库地址塞进配置文件"不是读写分离——这种"伪读写分离"出问题时排查极其困难,因为应用根本不知道某次读为什么读到了旧数据。

五、分库分表

当单表数据量太大(千万级以上)、单库写入压力太高、或者历史与在线数据互相影响时,会走到分库分表

分库分表解决容量但带来查询和事务代价

拆分策略

拆分维度拆分方式适用场景
按用户 ID 哈希user_id % N 决定分片数据按用户均匀分散
按时间每月一张表,每年一个库日志、订单等时间序列数据
按业务域用户库、订单库、支付库业务模块清晰
范围分片按 ID 范围数据有自然范围
一致性哈希用哈希环扩缩容影响最小

分库分表的真实代价

分库分表能分散压力,但代价是查询方式被彻底改变:

单库简单的事,分片后变难影响
SELECT ... WHERE id = ?需要先算出在哪个分片
跨分片 JOIN几乎不可能,改成应用层聚合
跨分片分页性能崩,所有分片都要查
全局唯一 ID不能用 AUTO_INCREMENT,要 Snowflake 等方案
全局事务跨库事务复杂,通常放弃
跨分片聚合COUNT、SUM 需要聚合所有分片
扩容迁移数据重新分布,复杂且影响业务

举例:"查所有用户最近 10 条订单"在单库就是简单的 ORDER BY + LIMIT,分片后需要查所有分片再合并排序——性能与复杂度同时崩坏。

走到分库分表前,先做完这些

很多系统其实根本不需要分库分表。在分片前,有一堆更低成本的优化:

单表大 → 加索引、改慢 SQL → 命中索引,问题解决
              ↓ 仍然慢
归档历史数据(冷数据搬到归档表)→ 在线表减小,问题解决
              ↓ 仍然慢
加缓存(热点数据进 Redis)→ 数据库压力下降,问题解决
              ↓ 仍然慢
读写分离 → 读压力分摊,问题解决
              ↓ 仍然慢
垂直分库(按业务拆库)→ 单库压力下降,问题解决
              ↓ 仍然解决不了
水平分库分表 → 不得不上的最后一步

只有前面所有手段都做完仍然撑不住,才真正进入分片

分库分表中间件

走到分库分表的项目,需要专门的中间件:

中间件类型
ShardingSphere(原 ShardingJDBC)JDBC 层,Java 生态主流
MyCATProxy 层
VitessProxy 层,YouTube 出品,K8s 友好
TiDB分布式数据库,自带分片
OceanBase分布式数据库

新项目对分片有需求时,优先考虑分布式数据库(TiDB、OceanBase)——比自己用 ShardingSphere 拼一套要省心得多。

六、缓存与消息队列

缓存挡住重复读,消息队列承接异步任务和削峰。这两类组件在架构里位置不同,但常常一起出现。

缓存

用途说明
数据缓存热点数据放 Redis,挡住反复查询
会话存储用户登录状态
分布式锁控制并发修改共享资源
计数器限流、统计、UV/PV
排行榜Redis ZSet
去重Redis Set、Bloom Filter

缓存接入后要面对的问题:

问题处理
数据过期与一致性数据库更新后主动失效缓存
缓存穿透缓存空值、布隆过滤器
缓存击穿互斥锁、热点 key 永不过期
缓存雪崩过期时间加随机抖动
大 key单个 value 过大,影响性能

详见 第 6 讲 数据库的出现 第五节。

消息队列

消息系统适用场景主要监控点
RabbitMQ后台任务、可靠投递、传统业务队列长度、未确认消息、消费者数
Kafka日志流、事件流、大吞吐数据管道消费 lag、分区均衡
Redis Stream/List轻量内部任务队列长度
RocketMQ国内电商、金融场景类似 Kafka

异步任务排查的不同思路

接入消息队列后,异步任务的排查思路与同步请求完全不同:

用户:点"生成报表"
接口:立即返回 task_id = 12345
前端:显示"生成中"

后台:消费者从队列取出任务 12345
后台:执行报表生成
后台:更新任务状态为 done

前端:轮询任务状态接口,看到 done
前端:展示报表

如果"生成中"一直转圈,排查的不是接口而是:

检查项含义
任务记录数据库里任务的状态是什么
消息队列积压消费者是否在消费
消费者实例后台服务是否正常运行
消费者日志是否有处理失败、抛异常
死信队列处理失败被丢到死信队列的消息

盯着前端页面没用,要去消息队列和消费者侧——这是异步架构排查的基本思维。

七、服务治理

服务一多,服务之间的调用需要统一管理,否则会成一锅粥。服务治理就是这一层的工作:

服务治理的核心能力

能力解决什么
服务发现服务名动态映射到实例地址
配置中心统一管理所有服务的配置
API 网关统一入口、认证、路由、限流
限流熔断防止慢服务拖垮上游
链路追踪看一次请求经过了哪些服务
集中日志多服务日志聚合查询
监控告警服务级指标 + 业务指标
服务网格(Service Mesh)把治理能力下沉到代理层

服务网格:治理能力下沉

Service Mesh(Istio、Linkerd、Consul Connect)把治理能力(限流、熔断、追踪、加密)做成应用旁边的 Sidecar 代理,应用代码不需要关心这些

传统方式               服务网格方式
应用 → 治理 SDK         应用 → Sidecar(治理逻辑)
        ↓                       ↓
       目标服务                目标服务

服务网格的价值:

  • 治理能力与业务代码解耦
  • 多语言统一治理(不需要每个语言都做 SDK)
  • 治理策略集中管理

但 Service Mesh 引入了复杂度,不到一定规模不建议上——小团队的微服务架构用传统 SDK 方式更轻量。

八、完整企业级请求的链路

一条企业级请求可能经过的对象:

企业级 504 排查要沿完整入口和后端链路收敛

公网用户

CDN(静态资源加速)

WAF(安全过滤)

云负载均衡(LB)

API 网关(认证、路由、限流)

Ingress / Nginx

后端服务 A(订单服务)

[配置中心读取配置]
[Redis 缓存]
[消息队列 Kafka]

后端服务 B(用户服务)

后端服务 C(支付服务)

数据库

每经过一个对象,都产生日志、指标、Trace 数据。架构图不只画对象,还要标注每一层的日志和指标在哪里收集——这决定出问题时去哪里查。

一次"接口 504"的完整排查路径

现象:用户报"提交订单接口 504"

第 1 步:看入口 Nginx 日志
  → status=504 upstream_response_time=60.000
  → 入口等待后端超时,问题在后端

第 2 步:用入口日志中的 trace_id 拉链路追踪
  → 整个链路:网关 10ms → 订单服务 50ms → 报表服务 60s
  → 慢在报表服务

第 3 步:看报表服务日志中的同一个 trace_id
  → 报表服务的日志显示卡在调用数据库

第 4 步:看数据库慢查询日志
  → 同一时间一条 SQL 扫了 3000 万行,缺索引

定位:报表查询的 SQL 缺索引,加索引后接口恢复正常

整个排查链路只用 4 步、几分钟,得益于完整的可观测体系(入口日志 + 链路追踪 + 应用日志 + 慢查询日志)。

如果没有这套体系,排查只能靠猜——这就是为什么"先建可观测,再上微服务"是云原生时代的标配。

九、企业架构的演进规律

总结一下企业架构形态的典型演进:

阶段形态典型规模
启动期单机部署 + 单数据库几百到几千用户
成长期多实例 + 负载均衡 + Redis 缓存几万到几十万用户
扩展期数据库主从读写分离 + 消息队列 + CDN几十万到百万用户
复杂期微服务 + 容器化 + 完整可观测百万到千万用户
大型化服务网格 + 多机房 + 分库分表 + 流量治理千万以上用户
超大型异地多活 + 全球加速 + 复杂数据中台海外大型互联网

演进的核心原则

每一步演进都要解决具体业务痛点——不要为了"看起来现代化"提前引入复杂度。

错误做法后果
5 人小团队上 K8s + 微服务运维负担超过业务收益
业务还在验证阶段就分库分表改业务时改架构,双倍工作量
月活 1000 的应用搞异地多活成本远超价值
没有 CI/CD 就上微服务部署成本指数级上升

合理的演进节奏:业务驱动技术演进,而不是技术驱动架构升级。看到痛点 → 评估方案 → 渐进式引入 → 验证效果。

技术债与架构演进

每一次架构升级都会引入技术债——旧代码、旧模式、旧组件不会立刻消失,长期与新架构共存。承认技术债,有意识地偿还,而不是假装它不存在:

  • 给旧服务设置"维护模式"(不再加新功能,只修严重 bug)
  • 安排专门的"还债"周期(每个迭代留 10%-20% 时间)
  • 关键路径优先重构,边角不动
  • 定期重新评估技术栈,该淘汰的尽快淘汰

健康的企业架构演进,是业务增长 + 技术演进 + 技术债治理三件事的平衡。