☰
三层四域架构总览图:从示意图到可执行系统骨架
2026/10/1 13:47:29 网站建设 项目流程

1. 这不是一张“示意图”,而是一份可执行的系统骨架说明书

你点开这个标题——“03-01-架构篇-整体架构总览”——第一反应可能是:又一张密密麻麻的方框+箭头图,配几行术语堆砌的说明,最后落款“仅供参考”。我干这行十多年,亲手画过200+套架构图,也拆解过上千份所谓“总览文档”,90%以上的问题不在于画得不准,而在于它根本没打算让人真正用起来。这张图不是用来汇报的装饰画,它是整个系统开发、运维、扩缩容、故障定位、甚至新人上手的唯一可信源。它必须回答五个硬问题:数据从哪来?经过谁?被谁存?被谁查?出错了往哪追?缺一个,就是埋雷。我见过太多团队把“总览图”当成PPT一页,结果上线后连缓存击穿发生在哪一层都得翻三天日志;也见过另一些团队,把这张图钉在工位墙上,每次加功能前先拿红笔在图上划路径,改完立刻同步更新——后者平均故障定位时间缩短67%,新成员两周内就能独立处理线上告警。所谓“总览”,核心不在“总”,而在“览”——要能一眼看清脉络,三秒定位模块,五分钟推演变更影响。它本质是一份动态契约:前端团队承诺只调用图中标明的API网关入口;数据库组只维护图中虚线框内的存储集群;运维组按图中颜色区分SLA等级配置监控阈值。今天这篇,就带你把这张图从“装饰品”变成“操作手册”。我们不讲抽象原则,只拆真实组件、真实流量、真实瓶颈、真实踩过的坑。你不需要是架构师,只要写过接口、配过Nginx、查过慢SQL,就能看懂、就能用。

2. 架构总览的底层逻辑:为什么必须是“三层四域”而非“七层八域”

2.1 所有复杂架构,最终都收敛到三个物理边界

很多人一上来就想画微服务、画Service Mesh、画Serverless函数链,结果越画越乱。真正的起点,永远是物理世界的约束。我带过的所有成功落地项目,其总览图骨架都严格遵循三个不可逾越的物理边界:

  1. 用户触达边界:这是所有流量的起点,也是唯一允许直接暴露给公网的区域。它只做三件事——SSL卸载、静态资源分发、最粗粒度的请求路由(比如根据域名或Path前缀分发到不同网关)。这里绝不能放业务逻辑,更不能存任何用户凭证。我曾接手一个电商项目,他们把登录校验逻辑放在CDN边缘节点,结果一次JWT密钥轮换导致全站登录失效47分钟——因为CDN节点缓存了旧密钥的校验结果,而刷新机制完全不可控。教训很痛:边界即隔离,隔离即安全。

  2. 业务处理边界:这是真正的“心脏区”,所有核心业务逻辑、状态计算、事务协调都在这里。它的关键特征是同构性——同一套代码、同一套配置、同一套依赖,在K8s集群里横向扩展。这里严禁出现“某个实例跑订单,另一个跑支付”的混部。我们用“服务网格”不是为了炫技,而是为了解决一个朴素问题:当订单服务调用库存服务时,如何确保100个订单实例发出的请求,能被均匀打到50个库存实例上,且失败时自动重试三次并降级?Service Mesh的Sidecar代理,本质上就是给每个Pod装了一个“交通协管员”,它不碰业务代码,却默默管理着所有进出流量的负载均衡、熔断、重试。没有它,你得在每个服务里重复实现这套逻辑,版本一升级,全网崩。

  3. 数据持久边界:这是所有状态的最终归宿,也是性能瓶颈的终极来源。它的设计哲学是分而治之,各司其职。关系型数据库(如MySQL)只存强一致性要求高的核心数据(用户账户、订单主表);Redis集群只存高频读、低延迟要求的热数据(商品库存、会话Token);Elasticsearch只存需要全文检索的非结构化数据(商品描述、客服聊天记录);对象存储(如S3兼容服务)只存大文件(图片、视频、日志归档)。我见过最典型的反模式,是把用户头像存进MySQL的BLOB字段——单张图片平均2MB,一次查询拖垮整个连接池。后来改成对象存储+CDN分发,数据库QPS下降83%,图片加载速度提升4倍。记住:数据不是越集中越好,而是越贴近使用场景越好。

提示:这三个边界在图中必须用不同底色明确区分(比如浅蓝/浅灰/浅绿),且用虚线框标出。任何跨边界的调用,必须标注协议(HTTP/gRPC/Kafka)和超时时间(如“库存服务→MySQL:gRPC 3s timeout”)。这是后续所有容量规划和故障排查的基准线。

2.2 “四域”划分:比“前后端”更精准的协作契约

光有三层物理边界还不够。团队协作中最大的摩擦,往往来自对“谁该负责什么”的模糊认知。我们用“四域”模型来固化职责,它直接映射到Git仓库、CI/CD流水线、监控告警分组:

  • 接入域(Ingress Domain):包含API网关(如Kong、APISIX)、WAF、CDN配置。负责人是平台工程团队。他们的KPI是:全链路平均延迟<100ms,DDoS攻击拦截率100%,API变更发布耗时<5分钟。这里不写业务代码,只写路由规则、限流策略、JWT解析逻辑。

  • 服务域(Service Domain):包含所有微服务(订单、支付、用户中心等)及其内部通信(Service Mesh控制面、注册中心)。负责人是各业务线技术负责人。他们的KPI是:单服务P99延迟<300ms,跨服务调用成功率>99.95%,服务间依赖关系图自动更新延迟<30秒。

  • 数据域(Data Domain):包含数据库集群、缓存集群、消息队列(Kafka)、搜索引擎。负责人是DBA与中间件团队。他们的KPI是:MySQL主从延迟<100ms,Redis缓存命中率>95%,Kafka Topic分区Rebalance时间<5秒。

  • 支撑域(Support Domain):包含日志中心(ELK)、链路追踪(Jaeger)、指标监控(Prometheus+Grafana)、配置中心(Nacos)。负责人是SRE团队。他们的KPI是:日志查询响应<3秒,全链路Trace采样率100%且存储>30天,配置变更推送延迟<1秒。

这四域在总览图中用不同图标表示(比如云朵图标=接入域,齿轮图标=服务域),且每个域内部模块必须标注负责人团队缩写(如“订单服务[ORD]”、“MySQL主库[DBA]”)。这样,当线上出现“用户下单失败”时,值班同学第一眼就能判断:错误码是502(接入域问题),还是500(服务域问题),还是超时(数据域问题),直接拉对应群,省去30分钟扯皮。

2.3 为什么拒绝“七层OSI模型”式架构图?

很多初学者喜欢套用网络七层模型来画架构图,结果画出一张“应用层→表示层→会话层→传输层→网络层→数据链路层→物理层”的示意图。这在教学上没问题,但在工程实践中是灾难。原因有三:

第一,它混淆了逻辑分层与物理部署。HTTP协议确实在OSI七层里,但你的API网关和后端服务可能部署在同一台物理机上,也可能跨三个可用区。总览图必须反映真实的部署拓扑,而不是协议栈理论。

第二,它掩盖了真正的瓶颈点。OSI模型里“传输层”负责TCP连接管理,但实际系统中,连接池耗尽、TIME_WAIT堆积、TLS握手耗时,这些真正在拖慢系统的点,OSI模型根本无法表达。而我们的三层四域模型,会直接标出“网关连接池大小=2000”、“服务间gRPC KeepAlive间隔=30s”、“MySQL最大连接数=5000”这些可量化的参数。

第三,它无法指导故障隔离。当系统雪崩时,OSI模型告诉你“可能是网络层问题”,这等于没说。而三层四域模型会清晰显示:“接入域→服务域的gRPC调用失败率突增至80%”,你立刻知道该去查网关日志和Service Mesh的mTLS证书是否过期,而不是去抓包分析ARP协议。

所以,这张总览图的第一条铁律就是:所有连线,必须对应真实的网络跳转;所有模块,必须对应真实的进程或容器;所有标注,必须对应可配置、可监控、可告警的实体。少一个,就是纸上谈兵。

3. 核心组件详解:从“方框”到“可运行的实体”

3.1 API网关:不只是流量入口,更是第一道防线

很多人把API网关当成简单的反向代理,配几个location规则就完事。实际上,它承担着远超预期的职责。我们以生产环境标配的APISIX为例,拆解它在总览图中必须体现的5个核心能力:

  1. 动态路由与灰度发布:总览图中,网关模块必须标注“支持基于Header/Query/Weight的灰度路由”。实操中,我们用X-Canary: trueHeader将5%流量导到新版本订单服务,同时监控新老版本的错误率、延迟差异。一旦新版本错误率超过阈值,自动切回100%老版本。这背后是APISIX的traffic-split插件,它不依赖服务端代码修改,纯网关层控制。

  2. 精细化限流:不是简单地“每秒1000请求”,而是多维度组合。总览图需注明:“用户ID维度QPS限流(100次/秒),IP维度并发连接数限流(50个),API Key维度日请求总量限流(10万次/天)”。这些规则通过APISIX的limit-count和limit-req插件实现,配置实时生效,无需重启。

  3. JWT鉴权与透传:网关必须完成JWT校验,并将解析后的user_id、role等字段注入HTTP Header(如X-User-ID),再透传给后端服务。总览图中,这条连线必须标注“JWT校验+Header透传”。我们曾因漏掉透传,导致后端服务重复解析JWT,CPU占用飙升40%。

  4. 请求/响应转换:前端调用的是RESTful API,后端微服务用的是gRPC。网关需完成JSON↔Protobuf转换。总览图中,若存在此类转换,必须用双箭头标注“JSON↔gRPC Protocol Buffer”,并注明转换插件名(如grpc-transcode)。

  5. 可观测性埋点:网关自身必须上报三类核心指标:① 入口QPS、错误率、P95延迟;② 各上游服务的调用成功率、延迟;③ TLS握手耗时、证书剩余有效期。这些指标直接喂给Prometheus,告警规则写死在Grafana里:“网关TLS证书剩余<7天,立即触发告警”。

注意:网关节点必须标注部署规模(如“3节点集群,跨AZ部署”)和健康检查方式(如“每5秒向后端服务发送HEAD请求”)。我踩过的最大坑是:网关健康检查用GET /health,而某服务的/health接口因数据库连接池满返回500,导致网关误判服务下线,流量全部打到剩余节点,引发雪崩。后来改成专用的TCP端口健康检查,彻底解决。

3.2 服务网格(Service Mesh):让服务“自己说话”

Service Mesh常被误解为“又一层代理”,其实它是服务自治能力的基础设施。在总览图中,Service Mesh不画成一个大框,而要体现其三大核心组件:

  • 数据面(Envoy Sidecar):每个Pod旁挂一个Envoy容器。它必须标注关键配置:① mTLS双向认证开关(生产环境必须ON);② HTTP/2连接复用开关(ON,减少TCP握手开销);③ 重试策略(gRPC默认3次,指数退避);④ 熔断阈值(连续5次5xx错误,熔断60秒)。

  • 控制面(Istio Pilot / Consul Connect):这是Mesh的“大脑”。总览图中需标明其高可用部署(如“Istio Pilot 3节点,etcd集群存储”)和配置下发机制(如“通过K8s CRD定义VirtualService,10秒内同步到所有Sidecar”)。

  • 可观测性集成:Mesh必须与Jaeger深度集成。总览图中,所有服务间调用连线,必须标注“自动注入Trace ID,采样率100%”。这意味着,当你在Jaeger里搜一个订单号,能看到从网关→订单服务→库存服务→MySQL的完整调用链,每个环节的耗时、状态码、SQL语句(脱敏后)都清晰可见。没有这个,分布式追踪就是空谈。

实操心得:Mesh不是银弹。我们初期在测试环境启用了Istio,结果发现Sidecar内存占用高达200MB/实例,而某些Java服务本身才300MB。后来换成轻量级的Linkerd(Rust编写),Sidecar内存降至40MB,CPU占用下降70%。选择Mesh,必须先压测Sidecar资源开销,再决定是否上。

3.3 数据存储:不是“数据库”,而是“数据服务矩阵”

总览图中的“MySQL”绝不能只是一个方框。它必须展开为一个服务矩阵,每个组件承担明确角色:

  • 读写分离集群:主库(1主)+从库(3从),通过Proxy(如MySQL Router)自动路由读写。总览图中,主库标注“仅接受写入”,从库标注“只读,延迟<100ms”。我们用pt-heartbeat工具实时监控主从延迟,一旦>500ms,自动告警并触发预案。

  • 分库分表中间件:当单库数据量>500GB或QPS>5000时,必须引入ShardingSphere或Vitess。总览图中,若存在分片,必须标注分片键(如user_id % 16)、分片数量(16库×4表)、以及分片路由逻辑(如“订单查询必须带user_id,否则报错”)。我见过最惨的事故:运营同学用“订单号”查订单,而分片键是user_id,导致查询扫全16个库,TPS瞬间跌到10。

  • 缓存双写一致性方案:Redis不是简单地“查不到就DB读”。总览图中,必须标注缓存更新策略:① 更新DB成功后,删除Redis缓存(Cache Aside Pattern);② 删除失败,投递MQ消息异步重试;③ 缓存穿透防护:空结果也缓存2分钟;④ 缓存雪崩防护:Key过期时间加随机偏移(如expire=3600+rand(100))。这些细节,直接决定系统能否扛住秒杀。

  • 冷热数据分离:订单表中,近3个月数据是热数据(存MySQL),3个月前是冷数据(存TiDB或ClickHouse)。总览图中,需用不同颜色区分,并标注迁移策略(如“每天凌晨ETL任务,将昨日订单归档至冷库”)。

实操提醒:所有数据库连接池配置必须在图中标注。我们曾因HikariCP的maximumPoolSize=20设置过小,导致高峰期连接池耗尽,大量请求卡在“获取连接”阶段,监控显示“数据库无压力,但服务超时”。后来根据压测结果,将连接池设为CPU核数×4,问题消失。

4. 流量走向与数据流向:用箭头讲清“谁动了谁的数据”

4.1 用户请求的完整生命周期:从点击到返回

总览图中的每一条箭头,都必须对应一次真实的网络调用。我们以“用户提交订单”为例,拆解其12个关键步骤,并标注每个环节的耗时目标与失败兜底:

  1. 浏览器发起HTTPS请求→ CDN节点(耗时目标:<50ms)
    失败兜底:CDN节点异常,DNS轮询至备用CDN

  2. CDN转发至API网关→ 网关集群(耗时目标:<30ms)
    失败兜底:网关全节点宕机,DNS切至备用网关集群

  3. 网关JWT校验 & 路由→ 订单服务(耗时目标:<20ms)
    失败兜底:JWT过期,返回401;路由失败,返回503

  4. 订单服务生成订单号 & 写入MySQL→ 主库(耗时目标:<100ms)
    失败兜底:主库写入失败,启动本地事务补偿(重试3次,记录失败日志)

  5. 订单服务调用库存服务扣减→ 库存服务(gRPC,耗时目标:<150ms)
    失败兜底:库存服务不可用,启用本地库存缓存(Redis),扣减后异步通知库存服务

  6. 库存服务写入Redis库存→ Redis集群(耗时目标:<5ms)
    失败兜底:Redis写入失败,记录本地日志,触发MQ重试

  7. 库存服务更新MySQL库存→ 主库(耗时目标:<80ms)
    失败兜底:MySQL写入失败,库存服务抛出异常,订单服务回滚本地事务

  8. 订单服务发送MQ消息→ Kafka(耗时目标:<10ms)
    失败兜底:Kafka不可用,消息存本地磁盘,定时重发

  9. 支付服务消费MQ→ Kafka Consumer(耗时目标:<200ms)
    失败兜底:消费失败,Kafka自动重试,重试3次后进入DLQ死信队列

  10. 支付服务调用第三方支付网关→ 外部API(耗时目标:<2s)
    失败兜底:第三方超时,支付服务标记订单“待支付”,前端轮询状态

  11. 订单服务更新订单状态→ MySQL(耗时目标:<50ms)
    失败兜底:更新失败,触发定时任务扫描未更新订单,补偿更新

  12. 网关返回JSON响应→ 浏览器(耗时目标:<10ms)
    失败兜底:网关渲染失败,返回预置HTML错误页

这张路径图,必须标注每个环节的P95耗时目标和失败兜底策略。没有兜底策略的箭头,就是风险点。我们曾因第5步“库存服务调用”没写兜底,导致库存服务升级时,所有订单创建失败,损失百万级GMV。

4.2 异步消息流:解耦的代价与收益

Kafka在总览图中绝不能只画一个“MQ”方框。它必须体现主题(Topic)治理和消费者组(Consumer Group)隔离:

  • Topic命名规范:总览图中,每个Topic必须标注命名(如order_created_v1、inventory_deducted_v2)和分区数(如16分区)。分区数决定并行度,必须大于等于最大消费者实例数。我们曾将order_created设为4分区,但消费者扩容到8实例,导致4个实例空转,吞吐量卡死。

  • 消费者组隔离:同一个Topic,不同业务消费,必须用不同Group ID。例如:
    order_created_v1→payment-service-group(支付服务)
    order_created_v1→logistics-service-group(物流服务)
    order_created_v1→analytics-service-group(数据分析)
    总览图中,这些连线必须用不同颜色区分,并标注Group ID。否则,一个Group消费失败,会影响其他业务。

  • 消息Schema管理:所有消息体必须用Avro Schema定义,并存入Confluent Schema Registry。总览图中,需标注Schema版本(如v1.2)和兼容性策略(BACKWARD)。我们曾因v1.3Schema新增必填字段,而analytics-service未升级,导致消费失败,数据丢失3小时。

  • 死信队列(DLQ):每个Topic必须配置DLQ Topic(如order_created_dlq_v1)。总览图中,需画出DLQ连线,并标注处理策略(如“人工介入,修复数据后重发”)。DLQ不是摆设,是最后的安全阀。

4.3 数据同步流:避免“双写”陷阱的终极方案

总览图中,凡涉及数据复制的连线(如MySQL→ES,MySQL→Redis),必须明确同步机制,杜绝“应用层双写”:

  • MySQL→Elasticsearch:采用Debezium监听MySQL Binlog,解析为Change Data Events,实时写入Kafka,再由Logstash消费写入ES。总览图中,这条线必须标注“Binlog CDC”,并注明延迟目标(<1s)。我们弃用过应用层双写,因为一次订单服务部署失败,导致ES数据缺失,搜索结果为空,客诉暴增。

  • MySQL→Redis:采用Canal监听Binlog,解析后投递MQ,由独立的Cache Sync Service消费,执行Redis写入。总览图中,这条线必须标注“Canal + MQ”,并注明幂等性保障(如“基于Binlog position + event id去重”)。应用层直接set(key, value),在分布式环境下必然丢数据。

  • 跨地域同步:若有多可用区,MySQL主从同步必须用GTID,避免传统position同步在故障切换后错乱。总览图中,跨AZ连线必须标注“GTID Mode ON”。

关键原则:所有数据同步,必须由独立于业务服务的CDC组件驱动,业务服务只读写单一数据源。这是保证数据最终一致性的铁律。

5. 常见问题与排查技巧实录:那些文档里不会写的真相

5.1 “总览图已更新,但线上行为没变”——配置漂移的幽灵

现象:架构图显示“订单服务调用库存服务走gRPC”,但线上日志却看到大量HTTP调用。排查发现,开发同学在application.yml里手动配置了HTTP地址,覆盖了Service Mesh的自动服务发现。

根因:配置管理失控。总览图描述的是“理想态”,而线上运行的是“现实态”。解决方案有三:

  1. 强制配置注入:在K8s Deployment中,通过envFrom从ConfigMap注入所有服务地址,禁止应用代码读取本地配置文件。我们用Helm模板生成ConfigMap,所有服务地址由CI/CD流水线统一注入。

  2. 运行时校验:在服务启动时,主动调用Service Mesh的控制面API(如Istio的/debug/config_dump),校验当前Sidecar配置是否匹配总览图。不匹配则panic退出。这招让我们在测试环境就捕获了90%的配置漂移。

  3. 定期巡检:SRE团队每周运行脚本,抓取所有Pod的/actuator/env端点,比对实际配置与Git仓库中Helm values.yaml的差异,自动生成报告。

实操心得:我们曾因一个spring.cloud.nacos.discovery.server-addr配置写错,导致订单服务注册到测试Nacos,而库存服务在生产Nacos,两个服务完全无法发现对方。花了6小时才定位。现在,所有配置变更必须走GitOps流程,人工修改直接被CI拒绝。

5.2 “图上画着3个Redis集群,但监控只看到1个”——资源视图割裂

现象:总览图标注“热数据Redis(3节点)、缓存Redis(5节点)、Session Redis(2节点)”,但Prometheus只监控到一个redis_up{job="redis"}指标,无法区分。

根因:监控标签体系未对齐架构图。解决方案:

  1. 统一标签注入:在Redis Exporter启动参数中,强制添加--redis.addr=redis://host:port --web.listen-address=:9121 --web.telemetry-path=/metrics --redis.password=xxx --redis.namespace=hot_cache。namespace标签直接对应总览图中的集群名称。

  2. Grafana看板分层:创建三个独立Dashboard:“Hot Cache Cluster”、“Cache Cluster”、“Session Cluster”,每个看板只查询对应namespace标签的指标。这样,值班同学一眼就能看出哪个集群异常。

  3. 告警分组:Alertmanager配置中,按namespace分组告警。hot_cache集群的redis_connected_clients > 1000告警,只会通知DBA团队;session集群的redis_memory_used_bytes > 80%告警,通知SRE团队。

5.3 “明明图上没画Kafka,但链路追踪里全是Kafka Span”——隐式依赖的陷阱

现象:Jaeger里看到大量kafka-consumer和kafka-producerSpan,但总览图中Kafka模块被画在角落,连线模糊。

根因:隐式依赖未显性化。Kafka不仅是消息队列,更是服务间的事实上的通信总线。解决方案:

  1. 强制显性化:总览图中,所有产生/消费消息的服务,必须与Kafka Topic之间画出实线箭头,并标注Topic名称和QoS(如at-least-once)。禁止用虚线或“消息总线”这种模糊表述。

  2. Span标准化:在Jaeger中,所有Kafka Span必须包含topic、partition、offset、group_id标签。这样,点击一个Span,就能直接跳转到该消息的完整上下文。

  3. Topic生命周期管理:建立Topic台账,记录每个Topic的创建人、用途、Schema、消费者列表、预计生命周期。总览图中,每个Topic旁标注台账编号(如TK-001)。我们曾因一个无人认领的legacy_order_eventsTopic积压2TB数据,占满磁盘,导致Kafka集群崩溃。

5.4 “图上写着‘跨AZ部署’,但故障时全挂了”——可用区理解偏差

现象:总览图标注“MySQL主从跨AZ”,但一次AZ故障,主库和从库同时不可用。

根因:对云厂商AZ定义的理解错误。AWS的AZ是物理隔离的数据中心,但某些私有云或混合云,AZ可能只是逻辑分区,共享同一套网络设备或存储。解决方案:

  1. 验证AZ物理隔离:在总览图中,跨AZ连线必须标注验证方式(如“通过traceroute确认网络路径不重叠”、“通过iostat -x确认存储设备ID不同”)。

  2. 故障演练常态化:每月进行一次AZ故障注入演练(如kubectl drain模拟AZ节点失联),验证总览图中的容灾策略是否真实有效。我们第一次演练时,发现MySQL从库的VIP漂移脚本有bug,导致故障切换失败。

  3. 多活架构前置:对于核心业务,总览图必须体现“单元化”设计(如按user_id哈希分片,每个单元包含完整服务+数据库)。这样,单AZ故障只影响1/N用户,而非全站。

最后分享一个小技巧:我们给每个核心组件(网关、服务、数据库)的K8s Pod添加了pod-topology-spread-constraints,强制同一服务的Pod分散到不同AZ。一行配置,胜过十页文档。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询