系统架构设计核心三要素:组件划分、关系设计与约束管理
聊系统架构的人很多但能把架构聊清楚的很少。很多人一张嘴就是“我们上了微服务”、“我们做了中台”、“我们要支撑百万并发”这些充其量是方案的名字不是架构的理解。真正把系统架构想明白的人通常会先回答一个很朴素的问题系统到底应该被拆成什么、各部分之间怎么咬合、哪些规则绝对不能变。我在过去十几年里接触过嵌入式固件、分布式网关、运营商计费系统也带过不少系统架构设计师考试的备考者最后发现一个规律不管系统是大是小、技术栈是C还是Java、跑在x86还是ARM上架构上真正要抓的“三个点”从来没变过——组件怎么划、关系怎么连、约束怎么定。这篇文章就是把这三个点掰开揉碎讲讲我自己的理解。1. 第一点组件划分——先把系统切明白1.1 组件划分到底解决什么问题组件是什么通俗点说就是系统里一个个能独立识别、独立交付、独立替换的功能单元。在不同的语境里它可能是微服务里的一个服务、嵌入式工程里的一个模块目录、Android APK里的一个独立库甚至是一个.so文件。组件划分决定的是系统的边界长什么样。很多人觉得组件划分就是把代码按文件夹分一分这是非常大的误解。组件划分不是整理抽屉而是在定一个系统里“谁和谁必须在一起、谁绝对不能和谁混在一起”的契约。这一步做错了后面所有的工作都在还债。我见过最典型的反面案例是一个流媒体后端项目。团队一开始为了赶进度把推流、转码、鉴权、计费全部塞进了同一个服务包里理由是“反正早期流量小拆开了还得写接口麻烦”。到了第二个月并发一上来转码导致CPU被打满连鉴权也一起超时第三个月上协议变更改推送逻辑的人不小心碰了计费代码线上直接出现了扣费异常。这不是代码质量问题是组件划分的边界问题——把变化速度不同、稳定性要求不同的东西绑在了一起。组件划分的核心作用有三个一是让系统具备局部化修改的能力改一个组件不影响全局二是让不同团队可以独立并行研发三是让系统能够按需弹性伸缩——流量大了扩容某个组件而不是把整个系统复制一遍。在嵌入式场景里这一点更明显STM32的固件如果按模块划分好调试外设驱动时就不用反复重新编译和验证整个应用逻辑反过来要是全都写在一个main文件里改动一个引脚定义都可能引发一连串奇怪问题。1.2 三种主流的划分逻辑与选择依据组件划分没有唯一正确的答案但有几种被反复验证有效的划分逻辑。按业务域划分这是微服务架构最推崇的方式围绕业务能力去切比如订单域、用户域、支付域。优点是边界天然清晰团队能对齐业务认知缺点是如果业务本身耦合度很高强行切分会把大量时间花在接口协调上。按技术层次划分这是最古老的划分方式把系统分成表现层、应用层、领域层、基础设施层。在嵌入式里就是驱动层、操作系统抽象层、应用层在Android里就是UI层、业务逻辑层、数据层。优点是层次清晰、依赖方向好控制缺点是如果过度分层每加一个简单功能都要穿透好几层开发效率会明显下降。按变更频率划分这个思路相对进阶先把稳定不变的底座框架、协议、公共库和频繁变化的部分业务规则、界面、外部对接分开。这样稳定部分可以沉淀成平台变化部分可以快速迭代。像是麒麟这类操作系统的架构内核和桌面环境分开、运行库和系统服务分开本质上都是为了隔离变更频率。怎么选我的经验是三个问题业务的语言是什么团队的组织结构是什么系统的变更热点在哪里如果业务语言清晰、团队也是按业务组织那就按业务域划分如果系统偏底层、以稳定为主那技术层次划分更合适如果系统已经进入了高速演化期那一定要在划定时给“变化的部分”单独留位置。绝大多数系统实际的划分逻辑都是三者混合的关键在于分清楚主导逻辑是什么而不是东切一刀西切一刀。1.3 组件划分里最常见的坑第一个坑是划分粒度失当。要么切得太粗一个巨型组件里什么都有内部没有任何结构性约束要么切得太细一个只有几十行的方法也要抽成一个服务结果线上的调用链绕成一团。我见过一个团队把“登录服务”和“注册服务”都单独拆出去了看起来服务很多项目很“先进”实际上这两个服务的调用方几乎永远是一对拆开的唯一效果就是白白增加一次网络跳转和一套部署运维成本。第二个坑是没有给划分标准留下文档。组件划分完了不记录为什么这么划过了半年新同事接手看着目录结构完全无法理解边界逻辑就开始凭感觉往现有组件里塞代码。时间一长边界模糊了各种跨组件的隐式调用也出现了。组件划分一定要留下架构决策记录写明边界依据、变更代价和演化方向。第三个坑是划分时过度依赖团队个人经验。很多时候组件的划分是“谁嗓门大听谁的”而不是基于对系统真实运行状态的分析。合理的做法是先梳理系统的主要业务流程和数据流向识别出候选组件的边界再通过团队的架构评审去确认。2. 第二点关系设计——连接比零件更重要2.1 关系的本质先从耦合说起如果说组件划分解决的是“系统有哪些零件”那么关系设计解决的就是“零件怎么咬合”。架构的核心从来不在零件本身而在零件之间的关系。关系设计要回答的问题包括组件A怎么调用组件B数据在组件之间怎么流动组件之间是同步等待还是异步解耦一个组件挂了其他组件会受到什么影响这些问题的背后全部指向一个词——耦合。很多新手对耦合有误解认为好的架构就是绝对的“零耦合”。实际上耦合是客观存在的处理耦合的关键不是消灭它而是把它管理在可控的位置上。没有耦合的系统是孤岛系统集成那天就是灾难。真正的问题不是“要不要耦合”而是“耦合的方向、强度、方式是否可以接受”。我举个具体的例子。早期我负责一个网关注册中心把服务发现、配置管理、注册信息全部耦合在一个数据库里。看起来是“统一管理”但一旦配置中心要独立升级数据库schema一变所有依赖方跟着遭殃。后来改成三个组件中间通过标准协议交互耦合从“共享数据库”变成了“协议约定”升级的爆炸半径一下子就缩小了。这里的本质不是减少了耦合而是把隐式的高风险耦合变成了显式的低风险耦合。2.2 接口与协议的设计要点关系设计最直接的落地方式就是接口和协议。接口是静态的契约协议是交互的规则放到分布式系统里就是API定义、消息格式、重试和超时机制放到Android这种场景里就是aidl接口、Binder调用的权限和线程模型放到嵌入式里就是芯片片内外设的寄存器映射、I2C总线的设备地址与读写时序。接口设计我始终坚持几个原则。第一接口要小而稳定少暴露内部细节接收方只信任接口承诺的语义不依赖发送方的实现细节。第二接口版本要显式管理尤其在移动端APK的API版本、框架版本、系统版本只要不一致就会出现兼容性风险所以上线之前一定要先确认目标设备的系统架构是arm64-v8a还是armeabi-v7a不同的指令集对应不同的so库生命周期。第三接口要能独立验证两边团队的联调不能等到集成阶段才开始接口文档、mock服务、契约测试要前置。协议设计里面最容易被忽视的是超时和重试。很多系统出故障不是组件坏了而是调用方在组件响应变慢时无限等待、不断重试结果把故障放大了。像电信计费系统这种实时交互非常敏感的场景超时阈值定多长、重试次数定几次、是不是需要熔断、要不要异步削峰这些都是关系设计的核心参数不是随便拍脑袋填的。2.3 依赖方向管理从代码里的坑讲到架构上的原则依赖方向是关系设计中最容易被忽视、也最容易累积出技术债的部分。依赖方向管理的核心原则是高层组件可以依赖低层组件抽象可以依赖抽象但业务实现绝对不能反过来依赖具体的别家实现。在嵌入式开发里这个原则体现得特别典型。STM32项目如果应用逻辑直接操作寄存器、直接引用某个驱动库的内部头文件那换一颗芯片、升级一次固件库应用层就得跟着改。合理的做法是应用层通过硬件抽象层HAL接口来访问外设底层驱动实现这个接口。看起来只是多了一层间接调用但换来的是应用层可以移植、驱动层可以独立替换。在分布式系统里依赖方向的问题就更隐蔽。最常见的反模式是“数据流向倒挂”——明明是对账组件需要从外部系统拉数据结果设计成了外部系统反向往对账组件推送明明是A服务依赖B服务的能力结果A把B的数据库表直接当作自己的数据源。这些都违背了组件边界的基本常识。每次看到这种设计我都会去追一下原始决策是怎么做出的几乎无一例外都是开发图省事、绕过接口直接操作底层资源导致的。3. 第三点约束管理——架构成败的隐形天平3.1 约束为什么重要以及为什么容易被忽略前面两个点讲的是“怎么把系统搭起来”而约束讲的是“哪些事情的边界必须被守住”。约束是指对架构起到限制作用的规则和前提包括技术栈选型、性能目标、部署环境、兼容性要求、团队规模甚至还包括政策法规与行业规范。为什么约束容易被忽略因为约束不像组件和关系那样可以画成图、写进文档它更像空气——平时存在感为零一旦违背了就会立刻引爆。一个系统可以组件划分得很漂亮、关系设计得很优雅但如果在约束上踩了红线前面的努力全部白费。我见过一个云计算管理平台组件化做得非常好团队也很有架构素养最后唯一没有做好的事情是忽视了国产化环境的适配要求等到要在aarch64架构的麒麟系统上部署时才发现依赖的第三方运行库没有该架构的版本落地进度直接受阻。3.2 现实中四类常见约束逐一拆解技术选型约束。这是最直接的约束解决的是“能用什么不能用什么”的问题。比如在嵌入式场景资源受限就不能随随便便上完整的操作系统内核要按Flash容量、RAM大小去选裸机、RTOS还是完整Linux在国产操作系统场景不能假设所有第三方依赖都有现成的arm64安装包Node.js这种运行时也要考虑有没有对应的aarch64构建版本。性能与可靠性约束。这类约束表现为SLA数字99.99%的可用性、P99延迟100毫秒内、支撑10万并发等。所有的架构设计都应该以这些数字为出发点去倒推——需要几副本、需要什么缓存策略、需要什么样的负载均衡与容错机制。兼容性约束。特别是在移动端、混合架构场景里API版本、系统版本、指令集架构共同决定了一个应用能覆盖多大的用户面。系统版本14、API 34、架构arm64-v8a这些参数看着枯燥但它们直接决定了APK里要打包哪些库、minSdk要定到多少、哪些新特性可以用、哪些必须做降级处理。演化约束。这是一个容易被忽略但极其重要的约束你希望这个系统未来三年走向哪里。如果明确知道系统要往多租户、多地域演进那么现在做数据模型和命名空间设计时就该留好扩展位而不是等到客户来了才推倒重来。演化约束站在“未来”的角度反向约束“今天”的架构。3.3 把约束变成可落地的架构决策很多团队不是没有约束而是约束散落在各处没有被统一管理。架构师真正要做的是把模糊的“感觉不能这么做”变成可执行的架构决策。处理约束的第一个动作是建约束清单。把当前已知的所有约束分类列出来包括硬约束和软约束。硬约束是不可妥协的比如“必须兼容ARM64指令集”“计费金额不能出现浮点误差”软约束是可以在特殊情况下放宽的比如“默认使用PostgreSQL但性能评测显示瓶颈时可以补充独立存储层”。约束清单要做到人人可见、变更留痕。第二个动作是给约束排优先级。当约束之间出现冲突时要有明确的裁决顺序。比如性能约束和技术选型约束经常打架——团队想用某个开发效率高的框架但性能测试不达标。这时优先满足硬性SLA还是硬性技术栈要求架构师必须在冲突发生时给出明确裁决而不是让开发团队自行摸索。第三个动作是把约束翻译成检查和测试。仅仅写在设计文档里的约束是软性的必须通过架构评审、CI扫描、接口契约测试、性能压测脚本把约束固化为“自动检查项”。拿API版本来说每次出包前都应该自动扫描目标SDK版本和运行库的abi匹配情况而不是等到线上崩溃才回头排查。4. 三个点在真实系统里是怎么协作的4.1 分布式系统里的三个点拿分布式交换机系统这类场景来说——关注这类系统的人通常关心网络数据平面的高吞吐和转控分离。组件划分上它至少分为管理平面、控制平面、数据平面三者不允许混布关系设计上三个平面之间通过标准化的接口通信数据平面负责快速转发控制平面负责路由计算和策略下发管理平面负责配置和运维约束管理上转发的时延上限、策略刷新的原子性要求都必须在架构层面测试和保证。这三个点看着是三条独立的工作流其实任何一个点被改动都会影响另外两个比如控制平面新增了一个协议数据平面就要配套增加转发表的下发格式下发格式变了管理平面的配置模型也得跟着调整。4.2 嵌入式场景下的三个点嵌入式是我觉得最能体现“三个点”普适性的领域。比如STM32的系统架构组件划分通常是存储管理、时钟与电源管理、外设驱动、任务调度这几块关系设计则集中体现为事件驱动或时间片轮转的关系模型中断服务函数和应用任务之间通过消息队列或信号量连接约束管理在这里表现得非常具体——Flash容量只有几十KBRAM可能只有几KB实时性要求又是毫秒级。这些约束直接决定了能否引入一个RTOS、要用什么粒度的任务划分、中断栈该分配多大。很多嵌入式工程师觉得架构是“上层那些做分布式的人”才需要考虑的东西其实一个main文件里函数之间的关系是不是清晰本身就是架构问题。再往大了看ARM64内核和麒麟这类国产系统是另一个嵌入式例子。aarch64和x86在组件划分上自然不同驱动层、适配层、系统服务层各管一段关系设计上应用通过系统API、ODBC之类标准接口访问数据库和外围设备约束上则必须考虑硬件生态和软件生态的双重兼容。为什么有些系统在x86上开发完迁移到arm64上就一堆问题根本原因是第三点——约束——没有被提前识别。这不是经验问题是架构认知问题。4.3 电信计费这类高并发系统的三个点电信计费系统是我做过的对架构理解要求最苛刻的方向之一因为它的业务价值极其直接扣错一分钱就是事故。也正因如此三个点在那里表现得毫无遮掩。组件划分上计费系统至少区分为在线计费、离线计费、账务管理、催缴与信控、对外接口网关。关系设计上在线计费鉴权消息必须低时延离线话单可以批量异步处理两个体系的数据最终要能够在账务层归并一致。约束上可用性要求接近电信级扣费事务不能丢、不能错、不能重复同时它还要与外部短信网关、支付通道持续对接每一个外部接口的协议变更都是约束变化。这类系统里只看组件划分不看关系设计会以为各组件可以独立升级实际上一次话单格式的版本变更就足以让全链路相互牵制只看关系设计不看约束又会忽略合规审计、对账时效这些硬性前提。4.4 移动端场景下的三个点移动端的系统架构经常被低估因为多数人只把它当成界面开发。其实从APK的安装包结构到Binder通信机制三个点一样不少。组件划分上一个应用要考虑壳工程、基础库、业务模块、动态库.so之间的布局关系设计上各模块之间的跨进程通信走了Binder还是LocalSocket决定了安全性和性能的取舍约束上API版本、系统版本、arm64-v8a还是armeabi-v7a的指令集选择、SDK包体积上限每一条都在约束你的架构决策。很多App在不同Android版本上出现闪退往往就是在第二个点关系设计引入了对某个系统API非预期版本的依赖同时第三点兼容性约束没有覆盖被依赖版本的变动。5. 三个点之间的动态关系5.1 先后顺序提出的顺序并不是随意的在实际做架构设计时我的习惯是先定约束再划组件最后理关系。为什么因为约束是外部世界给的硬边界你没法改变它只能适应它。先想清楚“什么不能做”才知道“能做的空间长什么样”再在这个空间里做组件划分才会做出真正可行的东西。最后再理清组件之间的关系因为关系是在边界和空间确定之后才能最终定型的。但理解一个现有系统时顺序要反过来先从组件划分去读系统的结构再从关系设计去读系统的行为最后倒推出这个系统当初是被什么样的约束限制着。这就像读一个人的档案先看学历经历结构再看做事方式行为最后才能推断出是什么环境塑造了他约束。所以“三个点”不仅是一个设计框架还是一个读系统的分析框架。5.2 相互制约任何一点的改变都会波及另外两点三个点不是孤立的。约束变了组件和关系必须跟着变。比如性能约束从P99延迟100ms收紧到50ms你可能需要拆出更细的缓存层组件或者把同步调用改成异步消息组件划分和关系设计同时改。组件变了关系和约束也可能被迫修改。比如把一个单体组件拆成两个服务原本的内存调用的关系就变成了网络调用的关系新的网络开销可能让延迟约束失守。关系变了组件也要调整。比如协议从REST改成消息推送那么接收方组件就需要新增消息消费能力甚至调整自己内部的模块划分。在做任何架构评审时我都要项目组回答一个固定问题“你这个改动动的是三个点里的哪一个另外两个会受到什么影响”能清晰回答这个问题的团队架构演进基本是可控的回答不上来的说明那个改动还停在一厢情愿的阶段。5.3 一个把三个点落地到评审会议的提问清单如果你也在做架构评审不妨把下面这些问题列成模板逐项过一遍关注点评审提问通过标准组件划分系统的核心组件有哪些彼此的边界依据是什么每个组件都能说清职责、归属方和变更边界组件划分组件是否能独立替换/升级替换代价是什么替换某个组件不引起上下游连锁改动关系设计关键调用链有几条每条链路的超时、重试、降级策略是什么每条链路都有明确的异常预案关系设计组件间的依赖方向是否符合高层依赖抽象的原则不存在业务实现反向依赖其它实现的代码结构约束管理系统的硬约束是什么软约束是什么是否记录所有关键约束都有文档、有负责人、有检查手段约束管理当约束之间冲突时优先级由谁裁决存在明确的决策机制与升级通道这份清单不是拿来凑数的而是每一个问题都能逼出实际情况。我带过几轮系统架构设计师备考的人经常发现大家能背出各种架构风格的定义但一问到“这个项目里谁和谁交互、什么变了会爆炸”就说不出来了。原因很简单他们是在背知识点不是在理解架构。三个点框架真正的作用就是让你在描述任何一个系统时不跑偏、不悬浮——每一步都踩在结构、行为、规则的具体事实上。6. 常见问题与避坑经验6.1 新手最容易犯的五个错误一、把组件划分当成项目管理分组。很多团队按“前端组负责的模块”“后端组负责的模块”去划分组件而不是按业务域或技术层次。这样划分的结果是组件的边界被人为耦合进了组织结构组织变了架构就乱了。二、只画图不讲关系语义。架构图上的箭头每个人都有自己的理解有的表示调用有的表示数据流有的表示部署依赖。标准做法是画图时配合一张接口清单表明确每条箭头的协议类型、调用方向、同步还是异步、数据格式、异常处理方式。三、把约束当成限制而不是资产。这是认知问题。约束不是给你添麻烦的东西恰恰相反约束是帮你降低决策成本的。你告诉团队“这里只能用公司统一的消息中间件”他们就不用再花时间和精力去比较五花八门的方案。约束越多选择空间越小反而更容易做出正确的决策。四、忽略非功能性约束。很多时候团队只看功能需求——要支持什么业务动作——而对性能指标、数据保留周期、安全合规这些非功能约束视而不见。这类约束往往是最难迁移重构的到了系统上线前才突然被验收标准打回代价极大。五、没有持续的架构守护。很多团队做完一次架构设计后就再也不管了直到两三年后代码腐化到不可维护才想起“当初不是定了架构吗”架构不是一次性的产出而是持续演进过程中的约定。它需要被定期检查、持续对齐。6.2 如何用三个点去拆解别人的架构理解别人的架构是架构师的基本功。拿到一个陌生系统从头到尾读源代码是不可行的我一般按下面这个流程走先找组件边界。看顶层目录、看部署清单找出系统由哪些独立运行或独立部署的部分组成。接着看主要入口和出口比如对外暴露的API列表、消息队列的Topic清单、数据库的表结构归属把这些当成关系设计的暗号。再倒推约束从部署环境、技术栈、运维脚本、SLA承诺里倒推出系统当时面临的硬约束。这个流程很管用。比如你在麒麟系统上看到某个服务的部署脚本里写了针对aarch64架构的判断逻辑就能立刻推断出系统跨架构部署的约束你在Android项目的gradle配置里看到abiFilters只留了arm64-v8a就知道团队对32位老设备的兼容策略是什么。看到约束再回头看组件和关系很多“为什么要这样设计”的问题就自然有答案了。6.3 备考系统架构设计师时三个点怎么用系统架构设计师考试是很多从业者都会面对的一道坎2017年下半年的案例分析试题一就是典型的架构分析题。我发现用三个点框架去解答案例分析题特别有效——拿到题目先不要急着看细节先抓三点系统的模块边界在哪里、模块之间通过什么交互、系统在设计时受到哪些硬性约束。把这三个点明确了再做问题回答思路会很清晰也不容易漏答关键得分点。当然考试只是阶段性的目标我更希望大家把三个点的理解用到平时的工作里。面试的时候被问到“介绍下你做过的最复杂的系统”先用三个点搭框架再说细节——第一部分说自己怎么划组件第二部分说组件之间怎么交互第三部分说自己处理过哪些约束冲突。这个回答结构比从早到晚按时间线流水账好太多了面试官一听就知道你是真的懂架构而不是只会熟练“服务发现那一套话术”。最后再分享一个我自己长期使用的习惯每当接手一个新系统不管大小我会先在黑板上写下三个词——组成、连接、限制——然后强迫自己用半小时把三个词填满。填写的过程就是对这个系统建立真正认知的过程。如果半小时后发现某一个问题填不出来说明我对这个系统的架构理解还没有到位这时候要做的不是继续讨论而是去查代码、查部署文档、查上线变更记录把事情弄清楚再继续。这个东西看似简单但在无数的项目里帮我避开了大量“看起来没问题、一上线就出事”的陷阱。架构是人写出来的也会被人改坏但只要你始终盯着组件、关系和约束这三个点系统在你眼里就不会失控。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →