首页 / 资讯中心 / 文章详情

系统架构的本质:一场关于边界与取舍的持续博弈

系统架构的本质:一场关于边界与取舍的持续博弈 ★ FEATURED ARTICLE
做了七八年系统架构相关的工作从刚入行时以为架构就是画几张分层图到今天面对复杂业务能沉住气做取舍中间经历了大量“当初怎么这么设计”的懊恼也积累了不少“幸好当时这么定了”的庆幸。说实话系统架构这个词被聊得太多了多到有时候我们反而忘了它本质上到底是个什么事。这篇文章不准备讲某个具体系统的设计方案也不打算罗列某种技术栈的选型对比就想以一个一线从业者的视角聊聊我对“系统架构”这四字的真实感想——它是什么、难点在哪、架构师每天到底在纠结什么、以及那些文档里不会写的取舍逻辑。如果你正在做系统设计、即将承担架构职责或者只是好奇为什么一个系统总会“改着改着就烂了”这篇内容应该能给你一些不一样的角度。1. 架构的本质一场持续的选择博弈1.1 “架构”被用滥了先界定一下它到底指什么很多刚接触这个领域的朋友喜欢问架构是不是就是画那种方框连线的图甚至有人觉得用了微服务就是有架构没拆分就是没架构。我在面试和评审会上见过太多类似的理解偏差。实际上架构并不是一个静态的产物它不是那几张架构图本身而是系统在诸多约束条件下做出的关键决策的集合。约束包括团队的规模、业务的阶段、资金成本、交付时间、现有技术栈的惯性甚至包括团队里每个人的经验水平。决策则涉及模块怎么划分、数据怎么流转、故障怎么隔离、升级怎么进行、某个业务规则该放在哪个层面执行。图只是把决策画出来真正的架构存在于决策背后的理由里。所以当有人拿着几张画得漂漂亮亮的部署图来找我看“架构”的时候我一般会先问几个问题为什么网关放在这一层为什么订单状态这个字段放在这个服务里如果这个服务挂了用户看到的是什么如果这个决策答不上来那这张图就只是张示意图不是架构。1.2 没有“最好”的架构只有“最不坏”的选择从业时间越长越觉得架构决策本质上是在互相冲突的目标之间找平衡。高性能和可维护性经常冲突高可用和成本经常冲突快速上线和结构整洁经常冲突。你选了一个方向就意味着放弃了另一个方向的某些好处。有些人以为存在一个“完美架构”能同时做到性能极致、成本低廉、扩展灵活、代码优雅、上线迅速——我可以直接说这是不存在的。每一次架构评审本质上是大家坐下来明确“我们愿意牺牲什么”。还拿我参与过的某订单中台项目举例当时业务要求我们做到单接口 99.95% 的可用性但团队只有八个人交付周期只有四个月。如果硬上完整的多活容灾架构光基础设施的复杂度就能把团队拖垮。我们最终的选择是核心下单链路做了完善的降级与重试机制非核心的报表服务允许适度延迟UI 上直接告知用户“高峰期数据可能略有延迟”。这是对外部可接受的“不完美”换来了核心链路在有限资源下的稳定。架构决策经常就是这样不追求理想而是追求在给定条件下让损失最小。2. 分层与边界架构师的第一课2.1 分层的本质是管理复杂度不是摆架子每个做过一段时间开发的人都会接触过分层表现层、业务层、数据访问层或者更细的端口适配器分层。但很多项目的分层最后都会名存实亡原因很统一——大家只把分层当成了代码目录结构而没有理解分层真正的价值是控制依赖方向。分层的本质是把“谁会依赖谁”这个复杂性前置把它变成一种明确的纪律上层可以依赖下层下层绝不能反向依赖上层同层之间最好也不要互相依赖。一旦基层的某个模块开始直接调用上层模块或者跨两层去访问数据短期内代码能跑、业务能交付但三个月后再看任何改动都会牵一发动全身。我见过一个典型的反面案例一个交易系统里为了让前端少改几个字段有人把后端序列化后的展示字段直接塞进了数据库表里美其名曰“冗余存储优化性能”。结果就是所有下游业务都开始依赖这个展示字段后来展示规则一调整连带三个核心服务的联调全部炸掉。这就是边界被击穿之后的连锁反应。分层不是为了好看它是为了让你在改一个功能的时候能确定地知道影响范围在哪、能不能安全地动刀。2.2 边界不清才是系统腐化的真正起点系统的“腐化”不是从某一次重构失败开始的而是从第一个模糊的边界开始的。比如支付回调服务和订单服务之间到底谁负责校验签名如果两边都做就会产生重复逻辑如果两边都不做就是安全事故。再比如积分服务和用户服务之间用户基础信息该由谁提供如果每个服务都缓存一份用户表迟早会出现某个服务的数据是三天前的这种诡异问题。所以我现在看一个系统第一眼不怎么看它的技术栈而是找几个关键业务流转链路问这里面每一步的数据归属、状态流转、幂等保障分别落在哪个模块。如果对方能清晰地说出来这个系统的架构底子大概率是健康的如果说“A调了B的库”、“这里直接拼了个SQL”那无论外层设计得多华丽内部一定埋着雷。边界设计的核心产出物其实就是职责归属的确定性——永远知道某件事在系统里由谁负责而这恰恰是很多团队忽视的。2.3 我在边界设计上踩过的坑关于边界我记忆最深的坑是在一个早期的会员系统中。当时图省事把会员等级计算逻辑同时放在了用户服务和营销服务里。上线初期没问题后来营销策略调整了积分倍数只改了营销服务的那份逻辑结果用户服务还在用旧规则计算导致一批用户看到的等级和实际权益完全对不上。排查了半天才发现是重复逻辑分叉了。这个问题的根源不是“改漏了”而是我们一开始就没想清楚“等级计算”这件事应该由哪个服务负责。边界不清晰时出了故障你连查都难查。从那以后我养成了两个习惯第一任何一个业务规则只允许出现在一个领域服务内其他服务只能通过接口获取绝不复制逻辑第二写架构文档的时候先写“职责边界表”把每个核心业务能力对应的归属服务、依赖服务、数据存储都列清楚。这个习惯救了我后续很多次也推荐大家可以试试。2.4 分层与边界的活学活用防腐层如果说分层是架构的第一课那么防腐层就是最实用的边界武器。防腐层这个概念很多初学者觉得遥不可及其实它的用途非常朴素你调用别人的系统、或者被别人的系统调用时不要直接把对方的数据结构和你的核心内部模型绑死。中间加一个翻译层对方修改字段、调整接口格式的时候你只需要改这个翻译层核心业务纹丝不动。我最近参与的一个物流对接项目就靠防腐层少走了很多弯路。承运商的接口响应字段一天一个样但我们给内部业务提供的数据模型从立项起就没改过。每次承运商调整团队只需要在适配层做一次映射修正。有人说这不就是加一层中间转换吗多此一举其实不然当外部依赖有多家、且每一家都不断变化时防腐层的价值会被急剧放大它能确保你的核心模型永远是“以你为主”的。3. 取舍的哲学高性能、高可用与高开发效率3.1 性能优化先找瓶颈堆机器是最后的方案性能问题是架构师绕不开的话题但你越到后面越会发现高性能的第一原则是“不先优化”。听起来反直觉其实就是不要在不知道瓶颈在哪的情况下就盲目引入复杂组件。缓存、分库分表、消息队列这些方案各有代价。缓存的代价是一致性分库分表的代价是跨库查询和事务复杂度消息队列的代价是链路追踪困难。把这些组件无差别地堆上去系统的复杂度会指数级上升而性能未必有质的提升。记得有一次对接外部流量高峰期系统响应突然变慢。当时团队里有同事提议上一整套缓存方案说这样能扛住压力。我让他先看监控发现数据库压力其实不高慢的是某个外部接口的响应而我们的代码里竟然用了同步调用的方式外部一慢整个线程池就堵住了。解决方案很简单把那个外部调用改成异步并加超时熔断响应时间立刻降下来。你看没有加任何新组件问题就解决了。真正的性能优化是从监控数据出发的“定点打击”不是天马行空的“全副武装”。3.2 高可用不是靠口号靠的是冗余设计和故障演练每一家公司都会说自己高度重视系统可用性但真到故障发生时能不能扛得住拼的不是态度而是平时有没有把冗余、降级、限流这些机制真正落地并且演练过。高可用的本质是“在部分组件失效的情况下系统整体仍然能对外提供核心服务”。要实现这一点冗余和故障转移必不可少。这里要纠正一个误区冗余不是简单的“多部署几个副本”。副本之间如何同步、如何选主、故障后流量怎么切换、切换期间未完成的事务怎么办每一个问题都需要提前设计。我之前做一个支付相关的服务时采用了主从结构但主库机房光纤意外被挖断从库上的数据落后了几秒导致一部分订单状态不一致。事后复盘发现我们从来没做过“机房级别故障”的演练只演练过“单机宕机”。自那以后团队立了个规矩每个季度至少做一次破坏性演练直接模拟最恶劣的情况让系统在真实故障面前暴露问题。高可用另一个常被忽视的点是降级策略要提前和业务方对齐。技术层面很容易实现“关闭非核心功能”但业务方未必接受“推荐位没了”这种体验。所以我做系统设计时会有意识地跟业务方约定一个“降级响应矩阵”什么级别故障下可以关闭哪些非核心功能用户会看到什么替代文案。有了这层共识故障发生时技术团队才能快速决策而不是战战兢兢地怕担责任。3.3 开发效率往往死于过度设计架构决策对开发效率的影响远比很多人以为的大。一个结构清爽的系统新功能从需求到上线可能只要几天一个结构混乱的系统改一个字段可能要全链路联调一周。但讽刺的是拖慢开发进度的最典型原因往往不是“架构太简单”而是架构被过度设计。启动一个新项目上来就规划 30 多个微服务、引入全套云原生基础设施、每个服务都要独立部署流水线——听起来很专业实际上团队每天光处理服务发现和配置同步就耗掉一半精力。微服务是手段不是目的。如果你的团队只有十几个人业务复杂度也没到必须拆分的地步老老实实用模块化单体边写着边观察边界等确实出现独立伸缩或者独立发布的需求了再逐步拆出服务这种“演进式”的路径可能更实际。判断是否该拆服务我的标准很简单这个模块是否经常独立变化是否和其他模块有完全不同的伸缩需求是否有独立的部署负责人三条都满足再考虑拆。否则捆绑在一个进程里反而更高效。4. 演进式架构没人能一次想清楚4.1 为什么“先写起来再说”往往是对的很多新人对架构有一种误解觉得高水平的架构师应该是在项目启动前就把所有设计都思考完毕然后一次性完美实现。这种观点特别像“建筑图纸先行”的逻辑。但软件系统和物理建筑有一个根本区别软件的需求在交付之前几乎必然发生变化。你以为你理解了业务但只要真实用户一上手新的反馈就会立刻出现。建筑打错了地基很难改软件却可以在开发中不断调整方向。所以我个人非常推崇演进式架构先做一个不复杂的、能跑通端到端流程的版本把每一层之间的接口用清晰的方式定义好然后根据真实的业务反馈不断演进。这里的关键在于“清晰接口”和“最小可用”不能冲突——接口清晰是为了后续演进有抓手先能跑通是为了业务反馈尽早进来。很多项目死在空转讨论上就是因为大家花了几周画蓝图最后发现画出来的东西根本不是业务想要的。4.2 重构不是架构失败的证据而是架构演进的常态我见过一些团队对“重构”有一种羞耻感觉得重构就说明当初设计错了。这种心态很要命。事实上架构的演进必然伴随重构。你对业务的理解在加深、团队规模在变化、技术栈在升级任何一项变化都会让既有结构出现不适配。能不能勇敢地在合适的时间点做调整直接决定了这个系统是越活越健康还是带病运转直到崩盘。当然重建和重构是完全不同的两回事。重构是在不改变外部行为的前提下调整内部结构重建是推倒重写。我见过一些系统因为核心结构实在无法调整最后只能重写代价极其惨重。所以当我听到有人提议“这个系统太乱了我们重写一个吧”的时候会非常谨慎。重写意味着所有隐性知识——各种边界情况、历史 bug 的成因、用户习惯——都要重新摸索一遍大概率会重蹈覆辙。比较稳妥的方式是先看清哪些部分真的是“坏而不该留”哪些只是“老但能用”然后小步快跑地逐步替换。4.3 如何让系统保持“可演进”三个关键动作既然架构注定是要不断演进的那最重要的能力就变成了“让系统永远有调整的余地”。怎么做到我总结了三个关键动作实践下来非常有效。第一是保持模块之间的松耦合。依赖方向要单一、模块间只通过接口交互、禁止跨层访问这些前面已经强调过但我要再加一句如果做不到完美解耦至少要让耦合点集中在“少数的几个位置”不要在几十个文件里各自耦合一些。第二是建立自动化测试的网络尤其是核心链路的测试。没有测试兜底的重构就像走钢丝不系安全绳平时没事一改就出事。有了一套快速执行的测试你在调整内部结构时才会有底气。第三是定期做架构守护用工具检查依赖方向、循环依赖、分层越权等问题把“架构走样”消灭在萌芽期。关于架构守护我用过一个很轻量的方式在 CI 里加一道检查凡是出现上层依赖下层的反向依赖、或者模块之间循环依赖的合并请求直接构建失败。当时推行这个规则的时候开发同事有些怨言觉得“有必要这么严吗”但坚持了一个月后大家发现代码库的依赖关系清晰多了改起代码来也不用瞻前顾后。架构纪律这种东西一旦靠工具固化下来团队的认知会很快统一。5. 架构师的软实力沟通、克制与判断力5.1 架构文档不是写给电脑看的是写给六个月后的同事看的很多架构师写文档的时候默认读者是两个“自己”——今天的自己和明天的自己所以写出来的东西晦涩难懂。但真实情况是六个月后读这份文档的大概率是一个刚接手这块系统的新同事他对业务背景毫无概念甚至连你们团队的技术约定都不清楚。如果文档里的名词不解释、决策背景不交代、关键取舍不说明那这份文档就是废纸。我写架构文档的经验是先讲“为什么”再讲“是什么”最后才讲“怎么做”。先说明我们当时面对什么约束、为什么拒绝其他方案、选择了现在这条路这能帮读者理解很多看似奇怪的设计细节再讲核心模块和它们的职责边界让读者对系统建立整体认知最后才落到具体的接口设计、部署结构、配置方式。同时文档里一定要有一章叫“已知的坑”把当初踩过、且未来很可能再踩的问题记下来。这一章的价值比正文还大。5.2 说“不”的艺术该拒绝的技术诉求和业务诉求架构师职位的核心权力某种程度上就是说“不”的权力。但怎么把“不”说好是个极其考验情商的活。业务方说“我需要实时统计给我上流式计算”如果你直接说“不行这个复杂度没必要”很容易让人觉得自己被否定了。比较有效的做法是先接住诉求再拆解背后的真实目标业务方要的到底是“实时”还是“半小时内能看到结果”如果是后者用普通的定时任务就能低成本搞定何必引入一套复杂组件。技术团队内部也一样。有人提议“这个查询好慢我们把数据库换成另一种架构吧”不要急着否定先问几个定量的问题现在的慢发生在什么时间窗口QPS 是多少P99 延迟是多少如果数据量和访问模式其实不大那大概率是索引和查询写法的问题而不是数据库本身的锅。架构师的判断力很大程度体现在能不能把模糊的诉求转化成精确的约束再判断当前方案是否对得上。拒绝之后还有一步容易被忽略给出替代方案。也就是说你说“不”的同时最好能给出一条路。哪怕那条路没那么惊艳但至少是可执行的。只说“不行”的架构师最后会被团队孤立能说“那样不行但我们可以这样”的架构师才真正有价值。5.3 架构师最该关注的三类反馈信号做架构越久越会发现自己最需要的能力不是“闭门设计”而是从各种信号里读懂系统状态。我整理了三类经常出现、但很多架构师容易忽略的反馈信号。第一类是“开发速度信号”如果一个需求明明逻辑不复杂但估算工期总是超出预期或者改一个小功能会牵连到很多模块说明边界已经不合理了。第二类是“线上稳定性信号”系统每次发布都战战兢兢、总在出一些“偶发问题”说明自动化测试覆盖不足、可观测性不完善。第三类是“团队情绪信号”如果团队里最有经验的开发者开始频繁吐槽“这代码简直没法改”这往往比任何监控指标都更早提出结构性问题。这三类信号一出现架构师就该主动介入而不是等着突发故障来提醒。关于可观测性多说一句很多系统在初建时只会记录“有没有报错”而没有记录“什么操作导致了报错”。我后来做系统都喜欢把关键业务的每个状态流转点都打点这样问题发生时可以完整还原现场定位速度能快很多倍。一开始团队觉得打点很烦但真出了几次生产事故后所有人都主动补上了日志。5.4 什么时候该坚持什么时候该妥协架构师如果永远坚持自己的方案会很累也容易变成孤家寡人如果永远妥协那这个系统迟早会变成谁都能插一脚的大杂烩。我理解中的判断平衡点是涉及用户不可见的基础性结构时要坚持涉及可逆的局部实现方式时可妥协。比如数据库选型、核心领域模型的归属、依赖方向的约束这些一旦定错推倒重来的成本极高风险极大所以必须慎重且坚持而某个内部接口是走 RPC 还是走消息、某个报表是用定时任务还是用临时脚本生成这些属于可逆的局部选择完全可以尊重团队习惯灵活一点。我的一个经验法则是架构评审会上如果有人反对某个方案先判断他的理由是否指向“长期不可逆的后果”。如果是那务必认真对待哪怕这意味着推翻重来如果只是“不习惯”、“写起来稍麻烦”而且方案本身能带来明确的长期收益那就礼貌而坚定地继续往前推。做架构的过程不怕意见多就怕分不清哪些意见值得推翻方案哪些意见只是口味偏好。6. 把这些感想沉淀成几条可执行的守则6.1 常见架构决策的权衡速查表这里我把日常工作中经常要面对的一些架构取舍整理成了一张速查表。不是为了告诉你标准答案而是为了提醒你任何选择都有代价做决策前先看看你愿意付出哪一边的代价。决策场景方案A方案B选择倾向新项目起步模块化单体微服务团队小于20人、业务未稳定时选A外部系统对接直接调用防腐层适配外部接口频繁变动、多供应商时选B跨服务数据共享直接查对方库通过接口获取除非性能极敏感否则选B状态一致性要求高分布式事务最终一致性补偿尽量避免分布式事务优先设计补偿数据量大、读多写少单库加索引读写分离/分库先看真实QPS和慢查询再决定非核心功能性能差优化代码引入缓存/新组件先量化瓶颈再决定引入组件定期跑批任务定时任务消息队列异步处理任务量小且可容忍延迟时选前者这张表里每一组选择我都至少见过真实项目因为选错方向而多花几周甚至几个月的时间去补救。核心还是那句话没有免费的架构决策。6.2 五条反复验证过的机构级守则最后整理五条我自己在执行架构工作时的铁律谈不上普适但至少在我参与的多个项目中被反复验证过先写职责边界表再写架构图。确认系统里每个核心业务能力的归属得到团队的共识后再画图、再讨论细节。依赖方向永远单向。这是代码质量层面最值得投入的守护用工具强制校验都不过分。核心链路必须有自动化测试。没有测试兜底的架构演进永远是在走钢丝。每一次架构决策都要记录当时的约束与取舍。六个月后你一定会感谢自己留下了这些上下文。架构与业务要共同演化。别指望业务不变也别指望架构一步到位保持响应变化的能力才是最重要的。做架构这几年最深的体会是架构师最重要的产出不是精美的设计文档也不是一张张高深的技术蓝图而是一个让团队可以持续高效交付、让系统可以在变化中保持稳定的演进环境。与其说我们在设计系统不如说我们在设计“团队与系统之间的协作方式”。很多时候你以为在解决技术问题实际上是在解决沟通问题、预期问题和组织问题。如果你正在为某个系统的架构熬夜挠头我自己的经验是先把所有约束条件摊开列一遍把“绝不能退让的底线”和“可以妥协的选项”区分清楚所有纠结大多会迎刃而解。架构不是炫技是负责任的权衡是对不确定性的坦然接受是在不断变化的世界里为系统找到一条能持续走下去的路。
阅读完成 · 觉得有帮助?
咨询建站