软件架构学习笔记(一):从技术选型到架构思维
过去一段时间,我在自己搓项目的时候,陆续接触了缓存、异步处理、RAG、高并发等不少技术和问题。但回头看,我对其中一些技术的理解似乎还停留在这样的阶段:
「哦,面试官喜欢消息队列做异步处理,写到简历上也好看,那我就引入 MQ。」
为了选型而选型,却很少认真思考:究竟是基于什么业务背景和痛点才需要引入这项技术?为什么选它,不选其他方案?引入之后又需要付出什么代价?
最近读了 Awesome Architecture 的几篇架构设计文章,感触颇深。
我开始意识到,架构设计的关键并不是知道多少种技术方案,而是能不能在做出选择之前明确问题和约束,在做出选择之后理解并承担相应的代价。这篇文章就结合其中的前两章,整理一下学到的知识,顺便记录一些自己的思考。
一、从实现思维到架构思维
说起来,除了刷力扣,我已经快一年没有怎么手敲过业务代码了。
随着 AI 编程的发展,很多过去需要自己花费大量时间编写的代码,如今可以交给 AI 快速完成。
当「怎么写」的成本不断降低,「写什么」以及「为什么这么写」就变得越来越值得思考。
至少对我来说,今后需要有意识地把更多精力放在系统设计和技术判断上,而不是只关注代码能不能跑起来。所谓架构思维,就是在动手之前,先把系统想清楚的能力。
1.1 实现能力与架构判断
实现能力和架构判断能力不是一回事,虽然它们之间存在很强的联系。
打个比方,如果把实现能力比作砌墙,那么架构判断就更像是设计一栋楼:承重墙放在哪里、地基打多深、未来能不能继续扩建,这些问题不是靠砌墙的熟练程度就能解决的。
两者一个很重要的区别,在于反馈速度与纠错成本。
- 实现层面的错误: 代码写错了,往往运行测试、查看报错就能发现,修改成本相对较低。
- 架构层面的错误: 问题可能随着业务增长、需求变化才逐渐暴露。到那个时候,修改的成本可能远高于修复一个普通 Bug,甚至需要对系统进行大规模重构。
因此,架构判断不仅要考虑眼前能不能实现,还要考虑这项选择对系统未来的影响。
1.2 架构到底是什么?
以前我一度以为,架构设计就是画几张唬人的架构图,再往里面塞一些中间件,看着比较牛逼就行。但事实并非如此。架构不是装饰,而是一个系统中那些重要决策的集合。也不是引入的中间件越多,系统就越有架构。
例如,同样是实现一个异步任务,如果某项设计决定了任务的可靠性、部署方式、状态管理和可扩展性,那么它就值得从架构层面认真考虑。
反过来,一个可以轻易替换的局部实现细节,即使技术上比较复杂,也未必属于重要的架构决策。
判断一项设计是否属于架构决策,可以重点观察三个特征:
- 难以更改: 一旦确定,后续调整需要付出较高的成本。
- 影响全局: 不只影响某个局部功能,还会影响系统中多个模块及其协作方式。
- 关乎质量属性: 直接影响系统的性能、可用性、可扩展性、可维护性等。
这三个特征不是严格的数学定义,而是一套很实用的判断标准。
归根结底,架构真正关心的不是系统画了多少个框,而是:系统快不快、稳不稳、扛不扛得住、改不改得动、贵不贵、安不安全。
架构图只是表达设计的一种形式,真正重要的是决策本身,以及决策背后的「为什么」。
1.3 如何训练自己的架构判断能力?
我觉得可以先从一个很简单的习惯开始:每做出一项重要的技术选择,都强迫自己回答两个问题。
第一,为什么是它?
为什么选择当前方案,而不是其他方案?是因为它确实符合业务要求,还是因为别人都在用、自己比较熟悉,甚至只是为了让技术栈更好看?
第二,代价是什么?
选择它之后,我获得了什么能力,又放弃了什么?引入了哪些复杂度?将来可能在哪里付出代价?
如果一个方案的理由只剩下习惯和跟风,却说不出具体约束与取舍,那大概率还没有真正想清楚。
二、架构判断的基本框架
知道架构需要做重要决策之后,下一个问题就是:有没有一套可以遵循的方法,而不是每次都凭感觉拍脑袋?
有,而且这也是我最近开始有意识遵循的一套思考流程。我觉得它对避免「许愿式编程」也很有帮助。
1 | 需求 → 约束 → 质量属性 → 候选方案 → 取舍 → 决策 |
各阶段分别回答:
| 阶段 | 需要回答的问题 |
|---|---|
| 需求 | 系统究竟要做什么? |
| 约束 | 有哪些不能忽视的现实条件? |
| 质量属性 | 系统需要做得多好? |
| 候选方案 | 有哪些可行的实现方式? |
| 取舍 | 每个方案能带来什么,又需要付出什么? |
| 决策 | 最终选哪个,为什么? |
需要记住的是:这不是一套走完就结束的流水线。当初合理的选择,未来未必依然合理,因此架构需要持续演进。回头看,我过去做技术选型时,几乎把前三步全跳了,直接开始比较候选方案。
比如遇到异步任务,就开始研究线程池和 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: 明确一个方案解决了什么问题、增加了什么代价,以及为什么值得。
- 灵魂六问: 规模、读写比、一致性、增长、失败代价、约束。
- 核心原则: 没有放之四海而皆准的最优架构,架构需要随着条件变化持续演进。
参考资料
主要参考:

