过去一段时间,我在自己搓项目的时候,陆续接触了缓存、异步处理、RAG、高并发等不少技术和问题。但回头看,我对其中一些技术的理解似乎还停留在这样的阶段:

「哦,面试官喜欢消息队列做异步处理,写到简历上也好看,那我就引入 MQ。」

为了选型而选型,却很少认真思考:究竟是基于什么业务背景和痛点才需要引入这项技术?为什么选它,不选其他方案?引入之后又需要付出什么代价?

最近读了 Awesome Architecture 的几篇架构设计文章,感触颇深。

我开始意识到,架构设计的关键并不是知道多少种技术方案,而是能不能在做出选择之前明确问题和约束,在做出选择之后理解并承担相应的代价。这篇文章就结合其中的前两章,整理一下学到的知识,顺便记录一些自己的思考。

一、从实现思维到架构思维

说起来,除了刷力扣,我已经快一年没有怎么手敲过业务代码了。

随着 AI 编程的发展,很多过去需要自己花费大量时间编写的代码,如今可以交给 AI 快速完成。

当「怎么写」的成本不断降低,「写什么」以及「为什么这么写」就变得越来越值得思考。

至少对我来说,今后需要有意识地把更多精力放在系统设计和技术判断上,而不是只关注代码能不能跑起来。所谓架构思维,就是在动手之前,先把系统想清楚的能力。

1.1 实现能力与架构判断

实现能力和架构判断能力不是一回事,虽然它们之间存在很强的联系。

打个比方,如果把实现能力比作砌墙,那么架构判断就更像是设计一栋楼:承重墙放在哪里、地基打多深、未来能不能继续扩建,这些问题不是靠砌墙的熟练程度就能解决的。

两者一个很重要的区别,在于反馈速度与纠错成本。

  • 实现层面的错误: 代码写错了,往往运行测试、查看报错就能发现,修改成本相对较低。
  • 架构层面的错误: 问题可能随着业务增长、需求变化才逐渐暴露。到那个时候,修改的成本可能远高于修复一个普通 Bug,甚至需要对系统进行大规模重构。

因此,架构判断不仅要考虑眼前能不能实现,还要考虑这项选择对系统未来的影响。

1.2 架构到底是什么?

以前我一度以为,架构设计就是画几张唬人的架构图,再往里面塞一些中间件,看着比较牛逼就行。但事实并非如此。架构不是装饰,而是一个系统中那些重要决策的集合。也不是引入的中间件越多,系统就越有架构。

例如,同样是实现一个异步任务,如果某项设计决定了任务的可靠性、部署方式、状态管理和可扩展性,那么它就值得从架构层面认真考虑。

反过来,一个可以轻易替换的局部实现细节,即使技术上比较复杂,也未必属于重要的架构决策。

判断一项设计是否属于架构决策,可以重点观察三个特征:

  • 难以更改: 一旦确定,后续调整需要付出较高的成本。
  • 影响全局: 不只影响某个局部功能,还会影响系统中多个模块及其协作方式。
  • 关乎质量属性: 直接影响系统的性能、可用性、可扩展性、可维护性等。

这三个特征不是严格的数学定义,而是一套很实用的判断标准。

归根结底,架构真正关心的不是系统画了多少个框,而是:系统快不快、稳不稳、扛不扛得住、改不改得动、贵不贵、安不安全。

架构图只是表达设计的一种形式,真正重要的是决策本身,以及决策背后的「为什么」。

1.3 如何训练自己的架构判断能力?

我觉得可以先从一个很简单的习惯开始:每做出一项重要的技术选择,都强迫自己回答两个问题。

第一,为什么是它?

为什么选择当前方案,而不是其他方案?是因为它确实符合业务要求,还是因为别人都在用、自己比较熟悉,甚至只是为了让技术栈更好看?

第二,代价是什么?

选择它之后,我获得了什么能力,又放弃了什么?引入了哪些复杂度?将来可能在哪里付出代价?

如果一个方案的理由只剩下习惯和跟风,却说不出具体约束与取舍,那大概率还没有真正想清楚。

二、架构判断的基本框架

知道架构需要做重要决策之后,下一个问题就是:有没有一套可以遵循的方法,而不是每次都凭感觉拍脑袋?

有,而且这也是我最近开始有意识遵循的一套思考流程。我觉得它对避免「许愿式编程」也很有帮助。

1
2
3
需求 → 约束 → 质量属性 → 候选方案 → 取舍 → 决策
↑ │
└──────── 业务或规模发生变化时,重新评估 ─────┘

各阶段分别回答:

阶段 需要回答的问题
需求 系统究竟要做什么?
约束 有哪些不能忽视的现实条件?
质量属性 系统需要做得多好?
候选方案 有哪些可行的实现方式?
取舍 每个方案能带来什么,又需要付出什么?
决策 最终选哪个,为什么?

需要记住的是:这不是一套走完就结束的流水线。当初合理的选择,未来未必依然合理,因此架构需要持续演进。回头看,我过去做技术选型时,几乎把前三步全跳了,直接开始比较候选方案。

比如遇到异步任务,就开始研究线程池和 MQ 哪个更好,却没有先明确任务能不能丢、是否需要恢复、并发规模有多大等问题。

如果评价方案的标准本身都不清楚,那么即使把各种技术的优缺点背得滚瓜烂熟,也很难做出合理的选择。

2.1 需求:不仅要知道做什么,还要知道做得多好

一切从需求开始。需求可以分为两个重要的方面:

  • 功能性需求(Functional Requirements): 系统要做什么。
  • 非功能性需求(Non-functional Requirements): 系统需要满足哪些质量、性能和运行方面的要求,其中很重要的一部分就是质量属性。

这条分界线,我觉得值得刻在脑子里。

功能性需求通常很直观,例如上传图片、查询订单、发送消息。但仅仅知道系统需要这些功能,还不足以指导架构设计。给 100 个人用,还是给 10 亿个人用?要求一秒内响应,还是等三秒也可以?数据丢失能否接受?是丢一张图片无所谓,还是一条记录都不能少?

这些问题的答案,才会真正影响系统应该怎样设计。

所以需要培养一种惯性思维:

看到「做什么」,立刻追问「做得多好」。

同样是一个图片服务,要求秒开、高并发、极高数据持久性,与只需要满足个人日常使用的场景,完全可能采用两套不同的架构。

2.2 约束:不是来添堵的,而是帮你排除选项的

我以前很容易把架构设计理解为寻找理论上最优秀的方案。或者更简单粗暴一点:别人都在用,那我也用。但现实是,我们永远需要在一系列具体条件下进行设计,而不是在真空里寻找最优解。这些条件就是约束。

常见的约束包括:

  • 团队规模与成员的技术能力
  • 开发周期与交付时间
  • 预算与基础设施成本
  • 合规、安全和监管要求
  • 已有系统与技术栈
  • 第三方服务的能力及限制

区分两个概念:

质量属性是我们希望系统达到的目标,约束则是设计过程中必须考虑的边界。

有意思的是,约束看似限制了我们的选择,实际上却能帮助我们缩小决策范围。约束越清晰,真正值得考虑的候选方案通常就越少。所以我现在觉得,设计系统时,不应该急着寻找技术上的正确答案,而是先弄清楚自己到底处在什么条件下。

2.3 质量属性:系统究竟需要做到什么程度?

质量属性描述的是系统在性能、稳定性、安全性、维护成本等方面需要达到的水平。

这里整理一张表。

质量属性 核心含义
性能(Performance) 响应有多快、吞吐有多高,常用延迟、QPS、吞吐量等指标衡量
可用性(Availability) 系统在多大比例的时间内能够正常提供服务,常用可用性百分比衡量
可靠性(Reliability) 系统能否持续正确地完成预期操作
持久性(Durability) 已经成功保存的数据,在故障后能否保留下来
可扩展性(Scalability) 负载增长时,能否通过合理增加资源维持所需性能
一致性(Consistency) 不同节点或副本对数据状态的观察需要满足怎样的一致性保证
安全性(Security) 能否抵御攻击、防止越权并保护敏感数据
成本(Cost) 开发、部署、运行和维护需要投入多少资源
可维护性(Maintainability) 系统是否容易理解、修改、测试和排查问题
可观测性(Observability) 能否根据日志、指标和链路等信息理解系统的运行状态
可演进性(Evolvability) 业务变化时,系统能否以可接受的成本持续调整

拿到一个系统,可以尝试把这张表过一遍,问自己两个问题:这个质量属性对当前系统有多重要?如果重要,需要达到什么具体目标?

比如支付系统:

一致性?余额、账务和交易状态不能随意出现错误,核心交易的正确性优先级很高。

可靠性与持久性?已经确认的交易不能无故丢失,失败后还需要具备恢复、追踪与对账能力。

安全性?身份认证、权限控制、敏感数据保护都是必须考虑的底线。

但换成一个个人博客,质量属性的优先级可能完全不同。没有必要为了追求极高可用性,就在一开始搭建一套复杂的多地域容灾架构。

并不是每项质量属性都要拉满,而是要根据业务明确哪些最重要,并尽量把目标转化为可验证的指标。

三、没有银弹,只有取舍

终于来到我觉得架构设计中最值得反复琢磨的一点:

没有银弹,只有取舍(Trade-off)。

所谓银弹,就是西方传说里,唯一能打死狼人的银子弹。可以理解为能够一次性解决所有问题的万能方案。但在软件架构里,这种万能方案基本不存在。大多数技术选择都不是只有收益,没有代价。一个方案改善某个方面的同时,可能增加其他方面的成本、风险或复杂度。

举几个常见例子:

  • 追求更高性能: 可以引入缓存降低数据库读取压力,但会增加额外的存储和维护成本,还需要处理缓存与数据库的一致性问题。
  • 追求更强的一致性: 在分布式环境下,可能需要额外的协调机制,并为此承担延迟或可用性方面的代价。
  • 追求更高可扩展性: 可以采用分片、服务拆分等方式,但系统复杂度、运维成本和故障处理难度也可能增加。
  • 追求快速上线: 可以暂时采用更简单的实现,换取交付速度,但某些选择也可能留下需要未来偿还的技术债。

这里还可以联想到分布式系统中经典的 CAP 理论。简单来说,当分布式系统发生网络分区时,无法同时保证线性一致性(C)和 CAP 定义下的可用性(A):如果坚持一致性,部分请求可能需要等待或失败;如果坚持继续响应请求,则可能无法保证读到最新数据。

就像三角恋,你不可能做出让所有人都幸福的决策。

(明明是我先来的!)

当然,这并不是说做架构设计是一件很悲观的事。恰恰相反,当我们接受任何方案都需要考虑代价时,思考方式就会从「寻找最好的技术」转变为「寻找当前条件下最合适的方案」。而且,取舍也不是说每次提高一个指标,就必然降低另一个指标。有些设计确实能够同时改善多个方面,只不过我们仍然需要检查它有没有隐藏成本。因此,一次真正有依据的架构决策,至少应该能够解释:

这个方案好在哪里?它牺牲了什么?为什么我认为这个代价值得承担?

我觉得这也是以后审视自己技术选型时,特别值得反复使用的一套标准。

四、实操:架构设计的六个关键问题

前面讲了需求、约束、质量属性和取舍,但实际面对一个新系统时,应该从哪里开始?很多时候,功能需求比较容易明确,但质量目标和现实约束不一定会被完整说明。因此,不能只被动接受已有需求,还应该主动追问那些足以影响架构设计的问题。

这里整理一下文章中的「架构师灵魂六问」。

问题 需要了解什么 影响哪些决策
1. 规模多大? 用户数、请求量、数据量、峰值流量 容量规划、部署规模、潜在瓶颈
2. 读写比是多少? 读多写少,还是写多读少? 缓存策略、数据模型、读写路径优化
3. 一致性要求如何? 写入后是否必须立即读到?能否接受短暂不一致? 事务边界、副本同步、异步处理方式
4. 增长预期如何? 用户和数据未来可能怎样增长? 扩展空间、演进成本与容量规划
5. 失败的代价是什么? 服务挂了、任务失败、数据丢失会怎样? 可靠性投入、故障恢复、容灾设计
6. 有什么约束? 团队、时间、预算、合规、已有系统等 排除不可行方案,确定现实边界

这六个问题不是每次都必须按顺序填写的表单,而是帮助我们避免遗漏重要条件的检查清单。

例如,系统的读写比并不直接决定一定要使用某种数据库,但能帮助判断是否需要重点优化读路径。

同样,预估用户数也不能直接证明必须采用分布式架构。是否需要扩展,还需要结合请求复杂度、实际资源能力和性能目标进行判断。估算是为了形成有依据的设计假设,而不是为了给某个预设的技术方案寻找理由。

我觉得这里最重要的认识是:没有脱离场景的最佳架构,只有在当前需求、约束和质量目标下更合适的选择。不要一开始就急着寻找正确答案,先把问题问清楚。而当这些问题的答案发生变化时,当初的设计也应该允许被重新审视。

五、写在最后

回头看,以前我遇到技术问题时,往往习惯直接开始研究解决方案。需要异步处理?研究 MQ。遇到性能问题?考虑 Redis。担心未来扩展?是不是该上分布式?但这些技术并不是不能用,而是在使用之前,我应该先知道自己究竟要解决什么问题。这次阅读对我最大的启发,就是让我开始有意识地调整技术选型的思考顺序:

先明确需求和约束,再确定质量目标;先比较候选方案的收益与代价,最后才做出技术选择。

我以后做技术选型时,先多问一句:

「究竟是什么问题和约束,让我有必要使用这项技术?」

省流版

  • 架构思维: 在动手之前,先思考系统应该如何设计,以及为什么这样设计。
  • 架构决策: 重点关注修改成本高、影响范围广、关乎质量属性的重要选择。
  • 决策流程: 需求 → 约束 → 质量属性 → 候选方案 → 取舍 → 决策。
  • 需求: 不仅要明确系统做什么,还要明确需要做到什么程度。
  • 约束: 团队、预算、时间、合规等现实条件,可以帮助排除不合适的方案。
  • 质量属性: 性能、可用性、可靠性、一致性、安全性、成本、可维护性等,需要根据业务确定优先级。
  • Trade-off: 明确一个方案解决了什么问题、增加了什么代价,以及为什么值得。
  • 灵魂六问: 规模、读写比、一致性、增长、失败代价、约束。
  • 核心原则: 没有放之四海而皆准的最优架构,架构需要随着条件变化持续演进。

参考资料

主要参考: