简介:这份PDF文档围绕企业总体架构展开,面向架构师、技术负责人及中高级研发人员,系统讲解业务架构、应用架构、数据架构与技术架构四大领域的定位与协作关系。作者以曾任职公司为案例,还原200人研发团队、200余台服务器、5地8机房的真实场景,剖析系统频繁故障、运维责任错位等问题的根源,并给出从架构现状梳理到中远期规划、领域建模的完整思路。资源包内含1个PDF文件,约615KB,内容涵盖商务模型、功能与应用架构、E-R图与数据状态图、物理部署、SOA与分层规划等模块,目录结构清晰,便于按章节查阅。目前已有3434人学习下载,适合需要理解企业级架构全貌、撰写架构文档或推动系统改造的读者参考借鉴。
1. 四层架构不是PPT上的四个框:从一次线上故障说起
业务架构、应用架构、数据架构和技术架构,这四个词几乎出现在每一份技术规划文档里,但真正能把它们拆清楚、落到代码和配置上的人并不多。我见过太多团队把四张图画得漂漂亮亮,结果一次订单重复提交的故障就暴露了:业务架构里没定义幂等边界,应用架构里服务拆分过细导致事务跨了三个服务,数据架构里没有唯一约束兜底,技术架构里的消息队列重试策略又是默认值。四个架构各画各的,线上出事时谁也说不清该改哪一层。
这篇文章面向的是需要把架构从文档落到工程实践的人——可能是刚接手系统重构的后端负责人,也可能是要从零搭建一套业务系统的全栈开发者。我会按“每层架构解决什么问题、产出什么制品、怎么验证它真的生效”这条线,把四层架构串成一套可操作的方法,而不是停留在名词解释。读完之后,你应该能判断自己手头的系统在哪一层欠了债,以及补债的最小动作是什么。
2. 业务架构:先画价值流,再谈领域边界
2.1 业务架构的产出不是组织架构图
很多团队把业务架构等同于“公司有哪些部门”,这是最常见的翻车起点。业务架构的核心是回答:钱和物从谁手里出发,经过哪些价值交换环节,最终交付给谁。它描述的是业务活动的流转,不是行政汇报关系。
我一般会先让业务方用动词短语列出核心价值流,比如“客户提交需求→需求被评估→报价生成→合同签署→履约交付→回款核销”。每个动词短语就是一个业务活动,活动之间的箭头代表信息或实物的转移。这张图不涉及任何技术选型,纯粹是业务语言。
产出物通常是一张价值流图和一份业务能力清单。业务能力清单的写法有讲究:用“名词+动词”结构,比如“订单创建能力”“库存锁定能力”,而不是“订单管理”这种模糊词。能力清单的粒度决定了后续应用架构的拆分粒度——能力拆得太粗,应用会变成单体;拆得太细,分布式事务会教你做人。
2.2 用事件风暴对齐业务语义
事件风暴是我用过最有效的业务架构对齐手段。具体做法是:找一面墙(或一块在线白板),用橙色便签写“领域事件”(已发生的事实,过去时),用蓝色便签写“命令”(触发事件的意图),用黄色便签写“聚合”(命令和事件之间的业务实体)。
一个可操作的步骤是:
- 先让业务方按时间顺序贴出所有“已发生的事实”,比如“订单已创建”“支付已完成”“库存已扣减”。
- 针对每个事件,追问“是什么命令触发了它”,把命令便签贴在事件左侧。
- 再追问“这个命令作用于哪个业务实体”,把聚合便签贴在命令左侧。
- 最后用连线标出聚合之间的引用关系。
这个过程会暴露出大量业务语义分歧。比如“库存已扣减”这个事件,业务方可能认为下单时就扣减,技术方可能认为支付成功后才扣减。这种分歧如果不提前对齐,到了应用架构阶段就会变成服务之间的扯皮。
提示:事件风暴的产出物是业务架构的输入,不是应用架构的输入。不要在这一步讨论用 Spring Cloud 还是 Dubbo,那是后面的事。
2.3 业务架构的验证方法:用场景走查代替评审会
业务架构图最容易犯的错是“画完就挂墙上”。我的做法是组织场景走查:挑三个典型业务场景(正常流程、异常流程、边界流程),让业务方和技术方一起在图上“走”一遍。
走查时只问三个问题:这个场景下,价值流是否完整?每个业务活动的输入输出是否明确?异常分支是否被覆盖?如果走查中发现某个活动没有明确的输出物,说明业务架构在这一块是空的,需要补。
走查记录要落成文档,每个场景一行,标注“已覆盖”“有歧义”“缺失”。这份记录就是后续应用架构拆分的验收标准之一。
3. 应用架构:从业务能力到服务边界
3.1 服务拆分的依据是业务能力,不是技术分层
应用架构最常见的错误是按技术分层拆服务:Controller 一层、Service 一层、DAO 一层,然后每层独立部署。这种拆法在业务简单时没问题,一旦业务变复杂,一个业务变更要改三个服务,联调成本指数上升。
正确的做法是按业务能力拆服务。业务架构阶段产出的能力清单,每一个能力对应一个候选服务。比如“订单创建能力”对应订单服务,“库存锁定能力”对应库存服务。服务之间的调用关系应该与价值流图中的活动依赖关系一致。
一个可落地的判断标准是:如果一个业务变更只需要修改一个服务,且不需要跨服务事务,那这个拆分粒度就是合适的。如果每次变更都要改三个以上服务,说明拆得太细;如果一个服务里塞了五个以上不相关的业务能力,说明拆得太粗。
3.2 用契约测试锁定服务边界
服务边界定下来之后,最大的风险是边界被悄悄侵蚀。比如订单服务直接查了库存服务的数据库,或者库存服务反向调用了订单服务的内部接口。这种侵蚀在代码评审时很难发现,因为代码本身能跑通。
我的做法是引入契约测试。每个服务对外暴露的接口(HTTP 或 RPC)都要有一份契约文件,契约里定义请求和响应的结构、错误码、超时时间。契约文件放在独立的仓库里,服务提供方和消费方都依赖这份契约。
# 订单服务对外契约示例(order-contract.yaml) service: order-service version: 1.2.0 endpoints: - path: /api/order/create method: POST request: contentType: application/json schema: customerId: string (required) items: array (required, minItems: 1) idempotencyKey: string (required) response: success: code: 200 schema: orderId: string status: string (enum: CREATED, PAID, CANCELLED) errors: - code: 409 reason: DUPLICATE_ORDER description: 幂等键重复,返回已有订单 timeoutMs: 3000契约文件写好后,用 Pact 或 Spring Cloud Contract 生成消费方和提供方的测试桩。消费方测试时用桩模拟提供方,提供方测试时验证自己的实现是否符合契约。这样任何一方想偷偷改接口,契约测试会先红。
参数说明:idempotencyKey是必填项,由消费方生成,通常用 UUID。timeoutMs设 3000 是经验值,超过这个时间订单创建接口的失败率会显著上升,需要检查数据库锁或下游依赖。
3.3 应用架构的部署单元划分
服务拆完之后,部署单元怎么划?我的原则是:一个部署单元可以包含多个服务,但必须满足“同时发布、同时回滚”的条件。如果两个服务必须一起发布才能保证兼容,那它们就应该在同一个部署单元里。
具体操作时,我会画一张部署矩阵:行是服务,列是环境(开发、测试、预发、生产),单元格里填部署包名称和版本号。如果发现某个服务在四个环境里的版本号总是不一致,说明它的发布流程有问题,需要收拢到统一的流水线里。
注意:不要为了“微服务”而把每个服务都独立部署。独立部署带来的运维成本是实打实的,如果业务量没到那个规模,合并部署是更务实的选择。
4. 数据架构:一致性边界比表结构更重要
4.1 先定一致性边界,再设计表
数据架构最容易陷入的误区是上来就画 ER 图。ER 图很重要,但它解决的是“数据长什么样”,不解决“数据在哪个边界内保持一致”。我一般先定一致性边界:哪些数据必须强一致,哪些可以最终一致。
强一致的数据放在同一个数据库实例里,用本地事务保证。最终一致的数据可以跨库,用消息队列或定时对账保证。这个边界一旦定下来,表结构设计才有意义。
举个例子:订单和订单明细必须强一致,放在同一个库。订单和库存扣减记录可以最终一致,因为库存扣减失败可以通过补偿机制回滚。如果把订单和库存放在同一个库用本地事务,虽然简单,但库存服务的写入压力会拖垮订单库。
4.2 用幂等表解决重复提交
重复提交是数据架构里最经典的坑。用户点两次按钮、消息队列重试、网络超时后客户端重发,都会导致同一条业务数据被创建两次。解决方案不是让前端禁用按钮,而是在数据层加幂等约束。
-- 幂等表结构 CREATE TABLE idempotency_record ( idempotency_key VARCHAR(64) NOT NULL, business_type VARCHAR(32) NOT NULL, business_id VARCHAR(64) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (idempotency_key, business_type), UNIQUE KEY uk_business (business_type, business_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 插入幂等记录,如果冲突则返回已有记录 INSERT INTO idempotency_record (idempotency_key, business_type, business_id) VALUES ('req-uuid-001', 'ORDER_CREATE', 'order-123') ON DUPLICATE KEY UPDATE business_id = business_id;逻辑说明:idempotency_key由客户端生成,每次请求唯一。business_type区分不同业务场景。business_id是实际创建的业务实体 ID。当重复请求到达时,ON DUPLICATE KEY UPDATE不会报错,但也不会插入新记录。应用层在插入后查询一次,如果business_id与本次请求生成的不一致,说明是重复请求,直接返回已有记录。
参数说明:idempotency_key长度设 64 足够容纳 UUID。business_type用枚举值,不要用中文。唯一键uk_business保证同一业务实体不会被创建两次。
4.3 数据架构的验证:对账任务不是事后补救
很多团队把对账任务当成出问题后的排查工具,这是本末倒置。对账任务应该是数据架构的常规验证手段,每天跑一次,发现不一致就告警。
对账任务的写法:从两个数据源分别拉取同一时间窗口的数据,按业务主键做全外连接,找出只在左边、只在右边、两边都有但字段不一致的记录。差异记录写入对账差异表,由人工或自动补偿处理。
# 对账任务核心逻辑(伪代码) def reconcile(start_time, end_time): left_records = query_order_db(start_time, end_time) right_records = query_stock_db(start_time, end_time) left_map = {r.order_id: r for r in left_records} right_map = {r.order_id: r for r in right_records} all_keys = set(left_map.keys()) | set(right_map.keys()) diffs = [] for key in all_keys: if key not in left_map: diffs.append(("ONLY_RIGHT", key)) elif key not in right_map: diffs.append(("ONLY_LEFT", key)) elif left_map[key].amount != right_map[key].amount: diffs.append(("AMOUNT_MISMATCH", key)) save_diffs(diffs) if len(diffs) > THRESHOLD: alert(diffs)参数说明:start_time和end_time建议按小时窗口,避免全量扫描。THRESHOLD根据业务量设,一般设为总记录数的 0.1%。差异记录保留 30 天,用于追溯。
5. 技术架构:选型是最后一步,不是第一步
5.1 技术架构的输入是前三层的约束
技术架构不是“我们用什么中间件”,而是“前三层架构对技术提出了哪些约束,我们用什么技术满足这些约束”。约束包括:一致性要求、吞吐量要求、延迟要求、可用性要求、成本要求。
比如业务架构要求订单创建必须幂等,应用架构要求订单服务独立部署,数据架构要求订单和库存最终一致。这些约束翻译到技术架构就是:需要分布式锁或幂等表、需要服务注册发现、需要消息队列。技术选型是在这些约束明确之后才发生的。
5.2 用容量估算驱动技术选型
技术选型最怕拍脑袋。我一般先做容量估算:日活用户数、峰值 QPS、平均响应时间、数据增长量。这些数字不需要精确,但必须有依据。
| 指标 | 估算值 | 依据 |
|---|---|---|
| 日活用户 | 10 万 | 业务方提供的推广计划 |
| 峰值 QPS | 500 | 日活 × 人均请求数 / 峰值集中系数 |
| 平均响应时间 | 200ms | 用户体验要求 |
| 日增数据量 | 5GB | 单条记录大小 × 日增记录数 |
| 数据保留周期 | 3 年 | 合规要求 |
有了这张表,技术选型就有了边界。500 QPS 不需要分库分表,单库加读写分离足够。5GB 日增数据量,3 年就是 5.4TB,需要考虑冷热分离。这些结论直接决定了数据库选型和存储方案。
5.3 技术架构的验证:混沌工程不是大厂专利
技术架构的假设(比如“消息队列不会丢消息”“数据库主从切换在 30 秒内完成”)需要被验证。混沌工程就是主动注入故障,观察系统行为。
小团队可以从最简单的开始:在测试环境随机杀掉一个服务实例,观察是否自动恢复;在消息队列里注入延迟,观察消费方是否堆积;把数据库主库只读,观察应用是否报错。
# 用 tc 模拟网络延迟(测试环境) tc qdisc add dev eth0 root netem delay 200ms # 用 kill -9 模拟服务崩溃 kill -9 $(pgrep -f order-service) # 观察指标:错误率、恢复时间、日志中的异常堆栈参数说明:delay 200ms模拟跨机房延迟,用于验证超时设置是否合理。kill -9模拟进程崩溃,用于验证服务注册发现是否及时剔除节点。这些操作只在测试环境做,生产环境需要更精细的故障注入工具。
6. 四层架构的避坑清单:五个血泪教训
6.1 业务架构缺失导致应用架构反复重构
现象:系统上线半年内,订单服务被拆了又合、合了又拆,每次业务调整都要改服务边界。
原因:业务架构阶段没有对齐价值流,业务方和技术方对“订单”的理解不一致。业务方认为订单从询价开始,技术方认为订单从支付开始。
解决:回到事件风暴,把“询价”“报价”“订单创建”“支付”四个事件的边界重新对齐。对齐后重新划分服务边界,这次拆分后至少稳定了一年。
6.2 应用架构的分布式事务滥用
现象:订单创建接口平均响应时间 2 秒,超时率 5%。
原因:订单服务、库存服务、优惠券服务用了 Seata 的 AT 模式做分布式事务,三个服务串行执行,任何一个慢都会拖垮整体。
解决:把强一致改为最终一致。订单创建只写订单库,然后发消息给库存和优惠券服务异步处理。订单状态先设为“处理中”,收到两个服务的确认消息后改为“已创建”。响应时间降到 200ms,超时率降到 0.1%。
6.3 数据架构的索引缺失拖垮查询
现象:订单列表接口在数据量到 100 万后从 50ms 恶化到 3 秒。
原因:查询条件用了customer_id和created_at,但索引只建了customer_id。数据量小的时候全表扫描也快,数据量大了就暴露了。
解决:建联合索引idx_customer_created (customer_id, created_at)。建索引时用ONLINE DDL避免锁表。建完后用EXPLAIN验证执行计划,确认type从ALL变成range。
6.4 技术架构的中间件版本不一致
现象:预发环境正常,生产环境消息消费延迟高。
原因:预发环境的消息队列是 4.8 版本,生产环境是 4.3 版本。4.3 版本的消费线程模型有差异,在高并发下表现不同。
解决:统一中间件版本,用配置管理工具锁定版本号。所有环境用同一份部署脚本,版本号从配置中心读取,不允许手工指定。
6.5 四层架构文档不同步
现象:新人接手后按应用架构图改代码,结果改坏了数据架构的一致性约束。
原因:业务架构、应用架构、数据架构、技术架构四份文档分别维护,变更时只更新了其中一份。
解决:把四层架构的变更纳入同一个变更流程。任何一层变更,必须评估对其他三层的影响,并在文档中标注变更记录。文档用 Git 管理,每次变更提交一个 PR,PR 描述里必须填写影响范围。
注意:架构文档不是写完就完了,它需要和代码一样有版本、有评审、有回滚机制。没有变更管理的架构文档,三个月后就是废纸。
7. 用架构决策记录把四层架构钉在代码库里
架构决策记录是我坚持了最久的一个习惯。每次做架构决策时,写一份 Markdown 文件,放在代码库的docs/adr/目录下。文件内容包括:决策背景、决策选项、最终选择、理由、影响范围。文件名用ADR-序号-短标题.md,比如ADR-001-订单服务拆分粒度.md。
这个习惯的好处是:半年后有人问“为什么订单服务不拆得更细”,不需要翻聊天记录,直接看 ADR。新人接手时,按时间顺序读一遍 ADR,就能理解系统演进的脉络。
我一般会在 ADR 里加一个“验证方式”字段,写明这个决策怎么验证是否生效。比如“订单服务拆分粒度”的验证方式是:统计每次业务变更涉及的服务数量,如果连续三个月平均超过两个,说明拆分粒度需要调整。
最后一个技巧:把 ADR 和契约测试、对账任务、混沌实验关联起来。每个 ADR 至少对应一个自动化验证手段。没有验证手段的 ADR 只是空想,有验证手段的 ADR 才是工程实践。
我自己踩过最大的坑是:早期觉得 ADR 是形式主义,懒得写。结果一年后系统出了故障,排查时发现三个架构决策互相矛盾,但谁也说不清当初为什么这么定。从那以后,我要求团队里每个架构变更必须配一份 ADR,不写 ADR 的 PR 直接打回。这个习惯坚持了三年,系统的可维护性明显提升,新人上手时间从两周缩短到三天。希望帮到你。
本文还有配套的精品资源,点击获取