Appearance
第6章:系统设计——从“功能堆砌”变成可维护产品
6.1 先模块化单体(Modular Monolith),再考虑微服务
在独立开发(Solo Developer)或团队初期规模较小时,过早拆分微服务是导致项目难产和破产的最常见元凶:
- 微服务带来了分布式事务、跨服务网络延迟、复杂的服务发现、难以统一本地测试以及陡增的云服务器成本;
- 最优解:模块化单体——将所有业务运行在同一个进程内,但在代码结构上按照清晰的业务领域划分子模块边界:
mermaid
graph TD
App["统一单体应用进程 Web API"] --> Identity["identity: 用户认证与权限"]
App --> Billing["billing: 订阅、计费与支付"]
App --> Analysis["analysis: 核心业务/AI解析"]
App --> Training["training: 训练与课件"]
App --> Notify["notifications: 邮件与Webhook推送"]必须坚守的软件架构铁律:
IMPORTANT
严禁任何模块在没有封装的情况下随意跨边界直接读写其他模块的数据库物理表!
每个模块必须严格定义自身对外暴露的公开 Service 接口契约,内部数据表对外完全黑盒隐藏。守住这条代码纪律,远比盲目部署十几个微服务容器重要得多。
6.2 同步处理与异步长任务架构
如果用户的某次操作(如上传复杂棋谱进行深度 AI 分析、或生成大型 PDF 报表)需要消耗 10 秒到 1 分钟,绝对不能让客户端 HTTP 请求一直挂起阻塞等待:
- 浏览器或客户端网关可能在 30 秒时自动抛出超时;
- 用户因看不到进度反复刷新页面,引发巨额算力重复浪费。
mermaid
sequenceDiagram
autonumber
participant Client as 客户端
participant API as Web API 服务
participant DB as PostgreSQL
participant Queue as 任务队列
participant Worker as 后台异步 Worker
Client->>API: 1. POST /jobs (提交长耗时分析任务)
API->>DB: 2. 插入任务记录 (状态: queued)
API->>Queue: 3. 将 job_id 投递至工作队列
API-->>Client: 4. 立即返回 HTTP 202 Accepted (附带 job_id)
Queue->>Worker: 5. Worker 拉取任务并更新状态为 running
Worker->>Worker: 6. 耗时 30s 执行复杂引擎运算
Worker->>DB: 7. 持久化分析结果并更新状态为 succeeded
loop 客户端轮询状态
Client->>API: 8. GET /jobs/{job_id}
API->>DB: 查询当前任务状态
API-->>Client: 9. 返回当前进度或最终结果
end严谨的状态机流转约束:
任务状态流转必须设计严格的单向状态机校验: $$\text{queued} \longrightarrow \text{running} \longrightarrow \text{succeeded} \text{ 或 } \text{failed} \text{ 或 } \text{cancelled}$$
- 状态一经进入终端状态(
succeeded / failed / cancelled),严禁被逆向更改或重复消费覆盖。
6.3 任务不保证“只执行一次”:至少一次交付与幂等处理
在真实的生产分布式物理世界中:
- 后台 Worker 可能在刚刚算完结果、尚未向队列回传 ACK 确认的瞬间遭遇宿主机断电宕机;
- 消息队列为了保证可靠性,必然会在超时后将该任务重新派发给另一台 Worker 再次执行。
核心结论:
没有任何现成的队列系统能跨硬件真正做到物理上的“绝对精准一次执行(Exactly Once)”!
工业界最可靠的设计范式永远是: $$\text{至少一次投递 (At-Least-Once Delivery)} + \text{业务幂等处理 (Idempotent Processing)}$$
- 为每个任务生成业务全局唯一编号;
- 在写入最终明细结果表时,依靠数据库主键或唯一约束保障重复写入时直接被静默吸收或更新,避免重复扣费。
- 如果需要强一致地同时修改数据库并发布异步事件,可采用轻量级的事务性发件箱模式(Transactional Outbox Pattern)。
6.4 画图之前先回答五个问题
在打开软件绘制花哨的系统架构图前,先拿起纸笔回答这 5 个决定系统命运的根本问题:
- 谁是系统的核心使用者?
- 支撑系统生存的最核心的三条主干业务流是什么?
- 在发生灾难崩溃时,业务最多能够接受丢失多少时长的数据?(RPO 指标)
- 系统在发生故障中断后,允许在多长时间内必须恢复服务?(RTO 指标)
- 作为独立开发者或小团队,系统每月维护所能承担的成本上限(服务器费用与运维工时)是多少?
6.5 实战任务:AI 分析 SaaS 架构设计与 ADR 撰写
任务交付物:
- 绘制三张标准架构图:
- 系统上下文图(Context Diagram);
- 系统内部模块组件图(Component Diagram);
- 异步分析任务处理时序图(Sequence Diagram)。
- 撰写两份正式的架构决策记录(ADR,Architecture Decision Record):
- ADR-001:《为什么当前阶段坚决选择模块化单体而非微服务》;
- ADR-002:《为什么采用异步任务队列与客户端轮询替代长连接同步等待》。