尧图精选

系统拆分与组合的艺术:从单体到微服务的拆合决策清单

🕒 发布时间:2026/10/1 20:19:19 📁 来源:尧图网络
写软件架构的人十有八九都会陷入同一种挣扎系统到底该拆成多大一块才算合理拆得太粗代码全挤在一起改一个功能要牵动全身拆得太细服务满天飞一个订单流转要调用七八个组件排查问题累到怀疑人生。我见过太多团队在架构很简单和架构已失控之间反复横跳根源往往不是技术方案不够先进而是没想明白系统拆分与组合这件事的底层逻辑。这篇内容想把我多年做系统设计时沉淀下的拆合经验、判断标准、踩坑记录一次性说透希望能帮你跳出为拆分而拆分的怪圈。先交代清楚这篇文章适合谁正在从单体转向分布式或微服务架构的中小型团队、被复杂业务逻辑折磨的后端开发、以及想学会用组合思路组织代码与模块的入门架构师。它不会给你一套放之四海皆准的银弹方案而是给你一套思考框架和决策清单让你在面对任何系统时都能用同样一套方法去拆、去组、去验证。1. 拆分的起点先回答为什么要拆而不是怎么拆很多团队犯的第一个错误是上来就讨论微服务框架、服务网格、领域驱动设计把拆分当成一场技术改造甚至当成KPI。但拆本身不是目标拆是为了解决某个具体的痛点。如果痛点不明确拆分方案必然跑偏。1.1 从一个文件能跑到一个团队在奔跑的必经之路最原始的单体系统长什么样可能就是一个Java Web应用或者一个Python Django工程里面塞着用户、订单、商品、支付全部逻辑。系统初期这种结构效率最高因为函数调用就是组合方式改一个模块可以直接引用内部类部署也简单一个包扔到服务器上就能跑。但项目演进到某个阶段问题会集中爆发。最典型的信号是开发效率指数下降一个需求涉及10个文件20个同事在同一个代码库里合并分支每次发布都要看谁动了底层数据表一个线上事故可能牵连所有功能不可用。这时候单体的简单性已经被共享耦合消耗殆尽拆分才成为必然选择。我个人的判断标准很简单当所有代码在一起所带来的沟通成本、发布风险、故障爆炸半径已经显著超过分布式部署带来的运维成本时你才有资格谈拆分。拆分的本质是用服务边界去约束人与人、人与代码之间的依赖关系让每个人能在自己的上下文里独立决策、快速交付。1.2 拆分的本质是管理复杂度不是追求技术时髦我见过有团队把系统拆成了几十个微服务每个服务都是hello world级别业务逻辑分布得支离破碎最后不得不再引入一套编排引擎把这些服务拼回去。这种拆法属于典型的用技术复杂度替代业务复杂度结果适得其反。系统拆分与组合本质上是控制复杂度的两种手段拆分降低单位复杂度的认知负担组合确保碎片化后的整体一致性。像微服务架构、分布式架构、模块化设计都是在用分的方式对抗大泥球但分完之后必须有一套组合机制把服务重新组织成可用的业务能力。如果你拆了之后不知道如何组合那拆分方案就是失败的。所以在动手拆系统之前先问自己这个系统当前最大的痛点是什么是团队并行开发冲突严重是某个模块崩溃导致全站宕机是某个功能需要独立扩缩容还是仅仅觉得别人的系统都用微服务了我们也得上如果答案是最后一种请立刻停止拆分计划。1.3 三种必须拆的场景与三种不该拆的信号根据多年的项目经验我把必须拆的场景归纳为三类你可以对照自测必须拆的场景典型表现拆分维度团队规模超过沟通带宽10人以上在同一代码库频繁冲突评审排队按团队边界拆业务模块故障爆炸半径过大一个内存溢出把登录、支付、推荐全拖垮按故障隔离边界拆独立进程性能需求差异悬殊报表分析要跑批商品详情要毫秒级响按资源需求拆独立部署单元而以下三种信号意味着现在不应该拆至少不应该大拆业务需求仍然极度不稳定核心流程还在反复探索拆出的边界很快会需要重构团队没有足够的DevOps能力拆成多服务后连基本的日志链路、监控告警都保证不了整个系统只有两三个人维护单体模式下的认知负担完全可以接受。一句话拆分从来不是免费的午餐。它把代码层面的复杂耦合转换成了网络调用、数据一致性、运维监控层面的复杂协调。只有当你确信自己能支付得起这些代价时才可以动手。2. 系统拆分的四个常用切面找到真正值得切的那条线确定了要拆之后第二个问题是沿什么线拆。行业里有很多现成的框架比如领域驱动设计的限界上下文、按业务能力划分、按子系统划分但落到实际操作层面我总结出了四个最常用的切面选对了切面拆分后的系统才具备稳定性和可演进性。2.1 按业务领域切让每个模块拥有清晰的上下文边界这是最推荐的主切面也是微服务架构和分布式架构设计中最经典的做法。核心思想是从业务能力出发把系统切成一个个高内聚、低耦合的领域模块。比如电商系统可以拆分成用户域、商品域、订单域、支付域、库存域。每个域内的数据表、业务规则、对外接口都相对独立域与域之间只通过明确的接口或事件合作。这种切法最怕什么怕切面切到了业务逻辑中间。比如在订单处理流程中把发送短信通知独立成一个服务这虽然能在物理上拆开但在业务上发送短信只是订单状态流转里的一个副作用它不应该被当作一个独立的业务域。如果拆出来的模块没有清晰的业务语义别人在使用的时候就无法判断这个功能该放哪个域最后仍然会刘接口满天飞。一个实用的判断技巧尝试用一句话说明这个模块是干什么的。如果这句话需要加上辅助支撑之类的修饰词说明它可能是一个技术分组而不是业务域。真正的业务域应该能被业务人员直接理解比如用户注册登录订单生成与支付库存扣减与回补。2.2 按变更频率切让经常变的逻辑不连累稳定的逻辑第二个切面容易被忽略但它恰恰是最能缓解发布恐惧症的切面。在一个单体系统里营销活动的规则每周变一次而底层的支付对账模块一个月动不了一次。如果它们挤在同一个工程、同一个进程里每次改营销规则都要把整个应用回归一遍支付模块明明没动却要陪着承担风险。按变更频率拆分就是让快变的部分拥有独立的发布周期和独立的数据存储。具体做法可以参考这样把高频变动的业务规则、配置策略、外部渠道适配层拆成独立服务把低频变化的核心主流程、基础数据、财务相关逻辑保留在较稳定的服务里。组合上高频服务通过接口或事件与低频服务协作这样每次改动可以小步快跑不会把稳定的核心搞得心惊胆战。这里要说一个反直觉的体会不是所有的共享都值得抽取。很多人喜欢把公共的用户校验权限检查抽成独立服务但权限逻辑通常属于低频变化强行抽成服务反而会让每次请求都多一跳网络调用。合理的做法是把它作为基础库打进各个服务中除非权限模型的变更频率已经高到需要独立发布。2.3 按资源消耗切让不同负载特性的模块各取所需电商大促期间商品详情页的访问量可能是订单模块的几十倍。如果把它们放在同一个应用里流量一上来整个进程的CPU和内存都会被拖垮订单处理也被殃及。按资源消耗切换的意思是把资源消耗模型差异巨大的模块拆开让它们可以独立水平扩缩容。最典型的是在线事务处理OLTP与离线分析OLAP的分离。比如用户在界面上触发的操作走实时服务数据仓库里的报表计算走批处理集群。它们的技术栈完全可以是不同的甚至数据库都可以隔离。这样你可以在大促时只扩容查询服务而不必把所有计算资源都摊给订单服务。实际项目里我用过一个更简单的经验法则连续三次监控到某个模块的CPU、内存、QPS曲线和其他模块明显不一致就值得把它加入到待拆分清单。比如搜索模块吃内存支付模块吃磁盘IO它们在一起一定会互相干扰。拆开后各得其所。2.4 按团队边界切康威定律不认人只认结构康威定律说了无数次系统架构会复制组织的沟通结构。如果负责订单的开发团队和负责库存的开发团队在不同的城市工作时间和汇报线都不同那么把订单和库存强行放在一个服务里一定会灾难——合并代码的沟通成本会消耗掉所有开发产能。所以第四个切面是组织切面。你的拆分边界最好与团队边界重合。每个团队对它的服务拥有清晰的产权代码由谁维护、线上告警由谁响应、数据由谁负责全都指向同一个团队。这也是微服务架构中自包含的核心理念之一。当然团队边界并不是一成不变的架构调整之前先梳理组织架构反而能让拆分更可持续。如果你是一家创业公司的三个后端硬要按大公司的几十个微服务模式拆那就是拿组织能力去硬抗复杂度扛不住的。3. 组合的艺术拆分后的系统如何合起来用拆完之后最棘手的问题出现了原本在一个进程里的函数调用现在变成了跨服务的接口调用原本在一个事务里的数据操作现在分散到了多个数据库。拆分后的系统必须通过某种组合机制重新形成完整业务否则就是自断经脉。这部分我重点讲三种组合手段以及它们各自适用的场景。3.1 组合手段一接口拼接——简单直接但别滥用接口拼接是最直观的组合方式服务A调用服务B的REST接口或RPC接口拿到数据后拼装返回。比如订单服务需要用户服务返回用户的收货地址直接通过HTTP或gRPC查询即可。这种组合方式在请求链路简单、依赖关系明确时非常高效。但接口拼接有个隐性问题一旦调用链变长就会变成串联。用户请求订单服务订单服务查用户服务用户服务又查优惠券服务优惠券服务还要查运营配置服务。任何一个环节变慢整个请求跟着慢任何一个环节挂掉整个链路直接失败。所以我在设计接口拼接时会给团队定三条规矩同步调用链不超过三次超过后必须引入异步化或数据冗余调用下游接口前先考虑缓存是否能解决把热点数据缓存到本地或Redis下游接口必须提供降级方案哪怕是返回旧的缓存数据也不能让整个链路空转。3.2 组合手段二事件驱动——把手拉手变成广播子如果说接口拼接是过程式组合A拉取B的数据那事件驱动就是结果式组合A发布事件关注它的服务自己消费。这种方式特别适合跨域状态同步、异步任务解耦、以及多个服务都要感知同一件事的场景。比如订单状态变为已支付可以发布一个订单支付成功事件库存服务扣库存积分服务加积分通知服务发消息所有下游自己订阅。事件驱动组合是分布式架构里面最优雅的合法之一但它对团队的要求也比接口拼接高得多。你需要先设计好事件模型理清事件的属性、版本、幂等键还要处理事件丢了怎么办事件重复来了怎么办这些问题。我见过不少团队从接口调用硬切到事件驱动后因为消息堆积和重复消费导致数据不一致又默默改回同步调用。所以我的建议是整体流程的主链路可以用同步接口跨域联动和可异步处理的旁路逻辑尽量用事件二者结合而不是二选一。3.3 组合手段三服务编排与数据聚合——把碎片重新组装成产品当多个拆分出来的服务必须协同完成一个复杂业务时就需要编排层Orchestration。比如订单提交这个动作可能需要同时校验用户状态、锁定库存、生成支付单、触发风控。你可以用一个独立的订单流程编排服务去依次调用或并行调用其他服务并把最终结果组合成一个整体响应返回给前端。这里必须区分编排和编舞两种模式编排是有一个主导者Conductor它负责告诉你下一步做什么编舞Choreography里没有主导者每个服务根据事件自行决定动作。对于业务复杂度可控、调试要求高的系统编排模式更易把握对于事件流比较自然、彼此松散关联的场景编舞模式更灵活。实际项目中我倾向于核心交易链路用编排外围联动用编舞因为核心链路需要明确的状态机和可追踪的流程快照。数据聚合也是组合的核心。拆分后的数据库分散在各个服务但前端往往需要一个聚合一屏数据比如订单详情既要订单字段又要商品图片、物流轨迹、商家店铺名。这时候要在后端做一个聚合层BFFBackend for Frontends或图形查询网关把多个服务的字段拼装好的结果一次性返回给前端。这里特别提醒不要把所有聚合逻辑都推到前端做否则App端要发起七八个请求网络耗时长弱网环境下体验会非常糟糕。3.4 组合的底层心法数据结构和算法都离不开组合思维聊到组合我不由联想到代码层面最基础的概念——组合类型。比如Python里把多个元素组合成列表、元组、字典这本身就是系统拆与合的微观体现。你会把一段数据切成键值对再通过映射关系组合在一起本质上和微服务之间的数据传递如出一辙。理解了这一点再看系统架构里的拆分与组合会有一种豁然开朗的感觉底层的数据组合模式和上层的系统分布式组合模式遵循的是同一套逻辑。我在学习组合数学的时候深深感觉到组合这个词在系统设计里的分量。组合算法教我们如何从有限元素中枚举出不同的子集而这恰好在业务中对应了动态模块编排——比如同一个营销系统要给不同用户展示不同模块安卓端的动态布局、后端的规则引擎、Agent架构中的工具选择本质上都是在做从可用集合中选出合适的组合。当你的系统被拆分成了大量可复用的能力单元组合算法就开始发挥威力不是每个场景都硬编码调用链而是根据规则动态决定把哪些能力拼装起来这就是最理想的可组合架构。4. 拆合失衡的真实代价那些年我们追过的过度拆分话题聊到这里必须直面一个很多人不愿承认的真相拆分过度比拆分不足更可怕。一个团队从单体走向微服务后最常见的结果不是变快了而是变慢了。我参与过的项目里就有这样一段教训。4.1 过度拆分的症状先看症状清单。如果你所在的系统出现了以下现象很可能是拆过头了一个简单的用户查询要同步调用四个服务光网络耗时就有几十毫秒数据一致性需要靠一堆事务消息和回调补偿来维持而业务根本不需要那么强的实时性运维同事每天部署二三十个服务发布窗口长到凌晨两三点一个需求从提出到上线因为要修改多个服务的代码、走多轮接口评审周期从三天变成了十天。这些症状的共通点是组合的成本淹没了拆分的收益。拆分本意是降低单点复杂度但当拆分后的服务数量脱离了团队的实际维护能力每个服务之间的组合与协调成本会呈指数级上升。分布式计算里有个定理叫CAP定理说的是在分布式环境下一致性、可用性、分区容忍性三选二。很多团队在拆分的瞬间并没有意识到他们已经在做这个选择了——他们拆掉了单体数据库换来分布式数据一致性负担然后又花大量精力去写分布式事务框架完全偏离了初衷。4.2 怎么判断是不是拆多了我自己的经验是用三个问题快速评估一个业务需求平均要改动几个服务如果超过3个就要警惕。服务间是业务协作还是数据搬运如果大量服务之间只是为了把数据从A搬到B说明聚合层设计失败或拆分粒度太小。线上故障时你多久能定位到根因如果跨服务排查一次超过半小时链路追踪形同虚设说明组合和可观测性没跟上。如果三个问题都亮了红灯别急着往系统上加更复杂的组合框架先考虑合并服务。把强耦合且变更频率一致的服务合并成一个大服务并不会让你丢脸反而会让系统更符合实际的人与组织结构。架构简单不是指文件少而是指一个开发者能快速理解整个系统的运行逻辑。4.3 纠偏案例从30个微服务缩到12个我之前接手过一个典型的过度拆分项目30个微服务每个服务大约只有几百行代码服务之间互相调用的关系图复杂得像蜘蛛网。订单模块为了拿到一个商品名称要经过三层调用。最终我和团队做了一次合并手术把逻辑上属于同一业务域的、且数据强一致要求高的服务合并到一起30个缩成12个。合并之后核心接口的P99延迟从180ms降到45ms每个月减少约20次跨服务变更评审线上问题排查时间从平均60分钟降到15分钟。这次纠偏让我意识到架构改进不是只做加法和减法而是要做再次组合。拆分出来的服务并不是永久核心随着组织成长、业务定型、技术能力增强服务的边界也需要重新调整。好的架构是动态的是不断在拆与合之间找平衡的长期过程。5. 拆合决策清单一套能落地的判断方法前文理论不少这里给你一套可以直接照做的决策清单。我在每次做系统拆分或合并方案时都会按这个顺序过一遍极大提高了方案的准确率。5.1 拆之前必答的六个问题我们做任何拆分动作前可以强制团队填写这样一张“拆合分析表”问题回答必须具体当前系统最大的技术痛点是什么例如发布一次需要2小时故障影响面覆盖所有用户这个痛点能否通过不拆来解决例如优化代码结构、引入依赖隔离容器、按需构建拆分后由哪个团队长期负责必须有明确的责任团队否则不拆拆分后用什么组合机制同步接口、异步事件、数据聚合层或者是混合数据如何拆分如何保证一致性明确数据归属与同步方式兜底方案是什么拆分后如何验证成功设指标比如发布频率、P99延迟、故障恢复时间回答不出来任何一项拆分方案必须暂停。去把问题搞清楚再动手永远比拆完再踩坑便宜得多。5.2 组合方式选择的决策树针对不同的业务形态我总结了组合方式的选择倾向同步链路短、响应要求高→ 接口拼接 缓存兜底跨域状态变化需要多方联动→ 事件驱动同时做幂等与重试流程固定且必须精确控制→ 引入轻量级编排引擎比如临时状态机不需要大而重的框架前端需要多源数据聚合→ 建立BFF层把碎片化接口组合成面向场景的API。用决策树不是为了让架构设计变成选择题而是为了逼你想清楚每个场景的特性。我见过不少团队用了很不合适的事件驱动方案仅仅因为领导说“微服务嘛要用消息队列”最后把简单的查询逻辑也改成异步推送徒增复杂度。适合比流行重要。5.3 用“演进式架构”的视角看待拆合最后一点认知也是我认为整套方法论里最重要的拆分与组合不是一次性的架构改造项目而是持续演进的过程。你现在定的服务边界在半年后可能已经不合时宜那时候再调整是正常的。很多团队把架构拆分搞成一次性大爆炸项目一两个月重构完然后从此冻结等业务变化了又动弹不得。演进式架构要求你每一步都保持可逆性。怎么理解可逆比如你拆服务时数据库没有先做逻辑隔离那物理拆分后想合并回去就很痛苦。反过来如果你把服务间的通讯协议设计得尽量通用API版本控制规范后续无论怎么拆合影响面都可控。构建一个系统让它随时可以被进一步拆分也随时可以被合并回整体才是架构很简单的真正含义。下面分享一点我个人的实操体会在做系统拆分与组合方案时永远不要只画架构图然后发给团队就完事。你要亲自走一遍关键路径的代码模拟一次新人在你构建的复杂系统里接一个新需求的全过程。如果这个过程中你需要翻过五六个服务的代码才能说清楚一行业务逻辑是怎么流转的那这个架构就是你亲手造出的不合理迷宫。拆与合最终是让人工作得更舒服而不是让图变漂亮。最后再给一个很实用的小技巧把“拆”和“合”两个字分开写在需求文档的封面上。每次有同事提出要引入新的中间件、新的编排框架时先问一句这个工具是在帮我们解决“拆分过多”的问题还是在帮我们解决“组合不畅”的问题大多数复杂的技术选型其实只是在一个本来就该被合并的边界上打补丁。认清这一点很多架构困扰都会瞬间变得简单。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →