资讯详情

架构选型不是技术比拼,而是复杂度分配:单体、微服务与事件驱动全解析

📅 2026/10/20 15:54:42 | 华诺云谱 👁 阅读
架构选型不是技术比拼,而是复杂度分配:单体、微服务与事件驱动全解析
关于架构这件事我过去很长一段时间是凭直觉做判断的。现代软件开发里常用架构的取舍比如单体还是微服务我一度觉得是潮流问题直到有一次评审我力推一个刚立项的项目直接上微服务理由是“现在大家都在这么搞”结果半年后团队被困在调用链排查和数据一致性对账里才意识到架构选型根本不是技术选型而是复杂度分配。这篇就当是我这几年反复复盘后的一次系统整理把真正高频出现的几种架构形态——单体与分层、模块化单体、微服务、事件驱动、无服务器/云原生——摆在一起讲清楚然后给出一份我自己实际在用的实践指南。适合刚带团队的开发者、需要做技术决策的负责人也包括还在纠结“要不要把单体拆成微服务”的朋友。1. 我为什么想重新梳理架构这件事1.1 架构决策的代价往往在一年后才浮出水面先讲个具体场景。我前几年接手一个内部订单系统刚上线时是典型的单体分层架构代码分割规规矩矩测试也能跑。半年后需求开始密集每次改一个功能要动到三四个模块发布窗口从半小时拉长到半天。团队第一反应是“架构太旧了应该上微服务了”后来复盘才发现问题根本不是架构形态而是模块边界从一开始就没划清。就算当时上了微服务拆出来的也只是一堆互相撕扯的“分布式大泥球”。架构这个行业的奇怪之处在于选择本身不产生即时效用它的效益或代价会在一年甚至更久之后才显现。很多团队做架构评审时只讨论“现在用什么”很少讨论“一年后这个选择会带来什么”。我后来给自己定了一条规矩任何架构决策都要写下预期收益和预期代价并标注一个复查日期。因为架构问题往往是延迟反馈问题你不写下来就永远是事后诸葛亮。架构的本质其实不神秘就是把系统切成若干模块并定义模块之间的连接方式。切得好需求变化时只改一个模块切不好每次改动都像在地铁高峰期换乘。大多数人争论该用单体还是微服务真正应该争论的是模块边界划在哪、模块之间的依赖长什么样。形态只是边界的外在表现。1.2 架构不是单点技术而是复杂度分配我后来养成了一个习惯把架构选择看作“复杂度分配”。单体把复杂度集中在进程内微服务把复杂度摊到网络和运维层事件驱动把复杂度转移到异步逻辑无服务器把复杂度上交给基础设施。没有哪种架构是“没有复杂度”只有“复杂度被放在哪里”。想明白这一点之后再看各种技术社区的争论会清醒很多。有人说微服务是银弹有人说单体才是正义这两类声音大概率都没有结合语境。我见过一个不到十人的团队硬是拆了二十多个微服务每天光处理服务发现和配置同步就精疲力竭也见过一个几万人在用的业务系统规规矩矩用模块化单体跑得又快又稳。架构是否合理取决于它是否匹配当前的业务阶段、团队规模和运维能力。所以这篇梳理里没有“标准答案”只有一套我反复验证过的判断思路。每个章节都尽量讲清楚三件事这种架构解决什么问题、它把复杂度转移到了哪里、什么信号出现时说明该换一种形态了。2. 单体与分层架构被低估的复杂度基线2.1 单体架构不是“老古董”而是复杂度最低的起点过去几年“单体”被很多人直接等同于技术债一提到重构就喊着“把单体拆成微服务”。这是整个架构话题里最被曲解的概念。单体的本质是所有业务逻辑在同一个进程内通过函数调用完成协作。它的优点是简单直接部署只有一个包测试不用Mock一堆远程接口事务可以直接依赖数据库本地事务链路问题在进程内就能定位。单体真正适用的场景其实非常宽团队在十人以内、业务生命周期还没有被验证、访问量波动不大、团队没有专职运维。在这些约束下单体就是性价比最高的选择没有之一。我经常对团队说一句话如果一个问题能在单个进程内解决就不要拆成两个进程。这是架构设计里最容易被忽略的第一原则。当然单体也会退化。退化信号很典型代码库变成“大泥球”模块之间互相引用改一个公共工具类要重新回归所有业务发布一次要被迫连带很多无关模块上线新成员入职三个月还搞不清改动影响面。这时候要解决的是模块边界问题而不是立刻上微服务。方向错了动作越猛代价越大。2.2 分层架构的核心约束依赖单向、边界稳定单体内部怎么组织最常见也最容易被用错的就是分层架构。分层架构的核心不是“建几个文件夹controller、service、dao”而是依赖方向必须稳定。经典分层是接口层、应用层、领域层、基础设施层上层依赖下层下层不能反向依赖上层。这个依赖方向一旦被破坏分层的意义就没了。实际操作里最常见的错误有两类。第一类是跨层调用比如订单应用层直接跳过领域层去操作数据库访问对象或者接口层里塞了大量业务逻辑把应用层当摆设。第二类是反向依赖典型表现是业务核心代码里到处是数据库连接、消息生产者这样的基础设施细节导致想换一个存储方案时牵扯到整个业务逻辑。修正的核心思路是依赖倒置。业务核心不依赖基础设施而是在领域层定义仓库接口由基础设施层的实现类去实现这个接口。这样即使你一开始只有一个数据库实现领域层也被保护起来了后续换库或者加缓存只是替换实现的问题。这个原则很多人在书本上见过但真正坚持做下来的项目很少等到想演进时才发现底层代码早已和数据库绑死。2.3 模块化单体在单体里提前划好服务边界比裸的分层更进一步的是模块化单体。它的思路是代码仍然部署成一个单元但在模块与模块之间建立严格边界。每个模块有自己的公开接口、私有实现、独立数据逻辑模块之间不允许直接操作对方的内部表。现代工程体系里实现模块化单体的手段很成熟。Java有模块边界和Maven多模块Go的项目目录布局本身就能约束依赖方向前端用Monorepo管理多个应用包本质上也是在推行模块边界。模块化单体的价值在于它保留了单体的部署和事务优势同时又逼着你把“未来可能拆成服务”的部分提前隔离出来。我做过一个很便宜的实践在单体里给每个业务模块分配独立的数据库Schema物理上还是一个库但Schema边界已经锁死后面真要切服务时只需要把对应Schema连表搬走。这个动作成本极低带来的演进余地却很大。很多团队说“先快速上线边界以后再说”最后真到了“以后”的时候边界已经长成钢筋水泥根本来不及说。3. 微服务架构分家容易过日子难3.1 微服务真正解决的问题和它真正引入的成本微服务这些年被推得很高但很多人还没有搞清楚它解决的问题就上车了。微服务解决的核心问题有三个独立部署、组织自治、故障隔离。附带的好处是技术异构不同服务可以用不同语言、不同存储、不同发布节奏。但每个好处都有价格。独立部署意味着要建设发布系统、灰度、回滚和版本兼容机制组织自治意味着服务之间需要明确的契约和跨团队沟通制度故障隔离意味着要有超时、重试、熔断、降级整套容错手段技术异构则意味着多语言带来的团队技能和运维知识成本同步上涨。所以做技术选型时不能只回答“微服务能带来什么”还要回答“我们有没有能力接住这些东西”。我习惯用一个很朴素的判断办法如果你们的服务部署还要靠人工登录服务器拷贝文件连基础环境自动化都没有建好那微服务一定让事情变得更糟。微服务不是起点而是复杂度和成熟度都到了某个水位之后才该考虑的方向。3.2 服务拆分粒度的判断依据不是越细越好如果确实要拆第一个容易犯的错是拆分粒度。很多人按技术层次拆拆出一个“API服务”、一个“数据访问服务”、一个“工具服务”结果业务逻辑被切成碎片一次简单的下单流程要跨越五六个服务。正确方向是按业务能力拆也就是从领域模型出发先划清限界上下文再识别每个上下文内的高内聚聚合。我常用的判断标准有三条。第一一个服务应该能独立变更改动它的内部逻辑不需要同步改别人别人改动也不影响它。如果你的服务之间还频繁联调那拆的就不是服务只是换了个进程的模块。第二一个服务必须拥有独立数据还共用一张表就说明业务边界没有被切断服务化名存实亡。第三服务粒度宁可粗一点不要细到“一人一个服务”因为服务之间的网络开销和编排复杂度会随着数量非线性增长。还有一句比较反共识的话当团队只有十几个开发者、业务域也没有明显分化时把模块化单体做好远比重金拆微服务合理。很多人把微服务当成了架构的全部其实它只是复杂度达到阈值后的阶段性解。阈值没到硬上等于用更贵的方式解决一个本可以用便宜方式解决的问题。3.3 分布式下数据一致性从2PC到SAGA再到Outbox这是微服务最大的隐形成本。单体里改订单和扣库存可以在同一个数据库事务里完成服务化之后这两个动作分属两个库数据库本地事务已经帮不上忙。业界最早走了一条弯路直接用两阶段提交协议协调分布式事务实践后发现它吞吐低、协调器容易成为单点很快就退出了主流。后来大家更多转向SAGA模式。SAGA把一个全局事务拆成一组本地事务每个本地事务执行完发布对应事件由下一个本地事务响应如果某一步失败就执行前面各步骤的补偿操作。实现方式有两种编排式和协同式。编排式由一个协调者负责推进流程适合步骤多、流程复杂的业务协同式完全靠事件联动适合链条短、参与者之间的依赖少的场景。另一个非常实用的工程模式是Outbox。业务表写入后在同一个本地事务里再写一条outbox事件记录后台任务把outbox里的记录发布到消息队列。这样既不用写“先发消息还是先写库”这种两难代码又能保证事件不丢。消费者侧再做幂等去重就能在工程层面接近“最终一致”。需要接受的事实是分布式系统里不可能有零延迟的强一致业务方案设计时必须为对账、补偿和“稍后生效”这种交互留出空间。3.4 网关、可观测性与服务治理配套工程不能省拆完服务一堆配套工程必须跟上。首先是入口网关负责统一路由、鉴权、限流和部分聚合。然后是注册中心和服务发现让服务之间的调用不要写死IP。再往深处是链路追踪一个trace id从网关贯穿到每一个被调服务查日志时按trace_id串联否则排障时间会成倍放大。这些技术到现在都有非常成熟的实现真正的难点是人和流程。跨团队接口变更的通知机制谁负责接口文档谁来维护线上出问题时值班团队怎么判断故障边界微服务治理的难点从来不是某个框架用不熟而是这些看起来边角料的协作规则。我见过不少团队技术栈搭得很漂亮最后因为一次跨服务的数据库字段变更没有通知到位直接酿成线上事故。4. 事件驱动架构异步解耦是把双刃剑4.1 事件驱动到底驱动的是什么事件驱动架构和服务化的关系非常紧密。服务化之后服务之间除了同步调用还可以用另一种协作方式发布事件。事件描述的是“已经发生的事实”比如“订单已创建”不是“请去创建订单”。后者是命令走同步调用更合适前者才是可以异步广播的。事件驱动解决的是多点耦合问题。过去服务A做完一件事要逐个通知B、C、D用事件驱动之后A只发布事件B、C、D自行订阅并按需响应。这个反转带来了真正的解耦A不需要知道下游是谁下游各自管理自己的反应逻辑。但解耦不是免费的。响应从即时变成了异步用户可能点了下单界面显示“确认中”要等一会儿流程里所有环节都要考虑消息丢失、重复、乱序。复杂度只是转移了不是消失了。很多人只看见解耦的甜没看见异步的苦这是事件驱动最容易翻车的地方。4.2 消息中间件选型的关键不是性能是语义做事件驱动绕不开消息中间件。市面上主流的产品我都接触过简单说下体感。Kafka侧重高吞吐与流式处理适合日志、事件流、数据集成这类场景RocketMQ在事务消息、延迟消息、消费重试等业务向能力上更顺手RabbitMQ轻量灵活适合内部流程的集成Pulsar在计算存储分离和多租户隔离上做了很多设计。选型时不要上来就比吞吐量更重要的是回答一组语义问题消息投递是“至少一次”还是“最多一次”能不能保证分区内有序消费失败支持重试和死信队列吗有没有事务消息能力这些才真正决定业务代码怎么设计。坦白说只要业务量没有到百万级每秒绝大多数中间件性能都够用反而是语义是否匹配业务需求值得花更多时间确认。4.3 幂等、乱序、重放三个细节决定生死事件驱动在生产环境里最常踩的坑有三个。第一个是重复投递。中间件一般承诺“至少一次”同一事件可能被投递多次所以消费者必须做幂等。我的约定是事件里带一个全局唯一事件ID消费端用自己的去重表或者存储引擎的唯一索引去挡重复。第二个是乱序。跨分区的消息天然无法保证顺序所以设计时要把同一个业务实体的事件按分区键路由到同一个分区比如订单号这样单个订单的事件是全局有序的。不要在系统层面强求所有消息顺序那是极高的代价且业务上基本不需要。第三个是重放。数据修复或视图重建场景需要从某个时间点重新消费历史事件这就要求事件本身是自描述的并且中间件的数据保留策略要足够长。不少团队直到出了事故才发现事件流只保留了一周根本无法重放这种坑最好在架构阶段就提前埋好。4.4 从事件风暴到领域事件建模事件驱动架构和领域驱动设计天然契合。我比较推荐的建模方式是“事件风暴”拉上业务人员和开发一起沿着一条业务时间线把曾经发生过的事情用便签贴出来比如“订单已提交”“库存已扣减”“支付已完成”。顺着事件反推才能找到触发事件的命令、需要操作的聚合、事件之间的因果关系。建模时有两个细节值得注意。第一事件命名必须用过去时表示它是一个已发生的事实而不是一个要求。第二事件尽量携带完整的业务上下文不要只放一个ID让下游再反查详情接口。如果下游处理一个事件还需要上游在线解耦就名存实亡了。这是我在好几个项目里亲眼见过的错误改起来往往牵一发动全身。5. 无服务器与云原生把复杂度上交给基础设施5.1 无服务器架构的适用边界无服务器是近几年把架构复杂度向上转移的典型代表。函数计算、托管消息队列、托管数据库把很多原本要自己维护的中间件交给平台对个人开发者、小团队以及流量波动明显的业务来说都是很大的解放。我身边不少独立开发者就是靠这套组合快速把产品跑起来的。但它有非常清晰的应用边界。函数冷启动对延迟敏感型业务不友好长连接场景不适合函数计算模型强一致事务型业务放到无服务器里会变得很拧巴。无服务器最适合的场景是事件驱动、间歇性执行、以及快速上线的产品原型。我的判断是无服务器不是“所有应用都应该去”而是“能用托管就不要自建”这种思想的合理延伸。5.2 容器编排、网关与服务网格现代云原生底座云原生架构里还包含一组基础组件。容器编排平台负责实例调度和扩缩容API网关统一入口和鉴权可观测性体系负责指标、日志和追踪服务网格则试图把服务间通信治理从业务代码里抽离到底层。服务网格值得单独说一句。它确实让熔断、限流、重试能力下沉到基础设施业务代码变得更干净但引入它也会显著增加运维复杂度和资源消耗。一个小团队想上服务网格先掂量一下有没有专职的平台工程师。不是技术不先进而是复杂度也要有对应的人力去接住。我见过太多团队把基建架得很豪华最后维护成本比业务开发还高。5.3 架构形态收敛之后决策并没有消失有人觉得上了云原生托管服务架构就“不用设计了”。我的经验是恰恰相反。托管服务解决的只是运维层面的重复劳动真正的架构问题一个都不会少函数的粒度怎么切、事件模型怎么定义、数据放关系库还是文档库、跨服务的一致性怎么兜底。基础设施只是接住了运行环境业务边界不会自动变清晰。举一个实际例子同样用函数计算把整条订单链路塞进一个大函数和拆成订单创建、库存扣减、支付回调三个函数差异非常大而这种拆分仍然是在做架构设计。所以我常和团队说一句话把能交给平台的都交出去把不能交出的领域建模、数据设计、一致性策略牢牢握在自己手里。6. 架构演进别指望一步到位要设计成可以长大6.1 保留扩展点延后高风险决策很多团队把架构当成终态一开始就想把未来三年的结构设计出来结果不是过度设计就是反复推倒重来。我更推崇演进式架构的思路做设计时保留必要的扩展点但把高风险决策尽量延后。具体来说先把系统做成模块化单体把模块边界、数据边界、接口边界定义清楚。等到真实出现三个信号之一——某个模块需要独立扩缩容、某个模块需要换技术栈、某个模块需要独立发布——再把对应模块切出来。切割本身是增量重构不是重构日。最怕的是“趁这次迭代顺手把架构升个级”这种想法会把风险无限放大。演进式架构的心法是当前阶段把未来可能变化的决策放在一个容易替换的位置上而不是直接把它实现成不可逆的稳定结构。说白了少一点“一步到位”的雄心多一点“到时候换得掉”的从容。6.2 康威定律与团队结构的互动康威定律经常被引用系统的结构最终会反映出开发它的组织的沟通结构。这个规律常被误读成宿命论好像组织架构是一切起点的因没法改。实际上相反当团队边界调整时系统边界应该跟着调如果希望系统长成某种形状也要主动调整团队的协作方式。所以在没有拆分团队条件的时候强行拆微服务长期一定会出问题。因为系统的维护职责还必须按照原来的团队划分代码拆了责任没拆最终还是要靠大量的跨组沟通来填坑。比较务实的做法是先让模块边界对齐团队职责等团队结构和业务复杂度都支撑起来再沿着已经划好的边界把服务切出来。另一个重要的治理手段是架构决策记录。我每次做架构决策都会写一份简短的记录包含背景、备选方案、选择理由、预期代价。它的价值不是文档本身而是逼着你在当下把“为什么”想清楚也让后来的伙伴不用靠考古去猜当初的设计意图。很多系统烂不是最初选择有多错而是原因彻底失传之后后人只能靠猜去改动。6.3 一条我自己验证过的演进路线最后给一条亲测有效的演进路线。第一步重构模块化单体为每个业务域划清模块边界、数据边界、对外接口边界。第二步为未来可能独立部署的模块增加隔离层禁止跨模块访问数据库必要时加防腐层。第三步选一个低风险、低依赖的模块作为第一个服务切出来逐步用同步或异步接口替换进程内调用。第四步建立可观测性体系再继续切剩余模块。这条路线里最需要警惕的是“切一个服务顺便重构整个架构”。把拆分服务和业务重构混在一起做会让变量爆炸出了问题都不知道该往哪个方向排查。一次只改变一件事是架构演进最基本的原则。哪怕慢一点也比半年后回滚不了强得多。7. 我给架构选型准备的一张决策清单7.1 选型前先回答四个问题每次参加架构评审我习惯先让团队安静下来回答四个问题而不是急着讨论框架。第一个问题团队现在有多少人这决定了能接住多大的运维复杂度。第二个问题业务域之间是否存在强一致性的真实诉求尤其是有没有涉及资金、库存这类不能“稍后自动对上账”的业务。第三个问题是否有独立部署、独立扩缩容、独立技术栈的真实诉求还是只存在于想象中的未来需求第四个问题未来一年需求大概率朝哪个方向变化是走向数据密集还是走向外部集成协同这四个问题回答清楚架构可选空间通常已经缩小了一半。真正的架构评审本质是在暴露和确认假设而不是在现场即兴决定未来半年的技术方向。很多架构失误都是因为关键假设没有被确认大家心照不宣地把想象当成了事实。7.2 一张简化版选型对照表这里放一个我常用的选型表帮助快速做初次过滤。它不能替代深度分析但能在会议现场把那些明显不匹配的方向先排除掉。架构形态推荐前提主要成本单体分层起步阶段、团队小、业务未验证后期模块边界容易腐化模块化单体单体已出现边界混乱但团队暂不具备服务化能力依赖纪律约束模块间依赖微服务有明确的独立部署/扩缩容诉求团队跨多个业务域且规模足够网络、运维、分布式一致性成本事件驱动跨模块异步通知、审计、重放需求多个下游消费同一事实幂等、顺序、失败补偿等异步复杂度Serverless/云原生流量波动大、事件型任务、团队运维人力有限冷启动、供应商绑定、成本预测难这张表只是一个初筛。落地方案时还要把一致性要求、可观测性投入、团队运维能力三个约束一起放进判断。我见过太多团队先选中了一个架构再为它找理由这是反着来的。正确顺序应该是从约束出发推结论。7.3 决策之后设定“失效信号”并持续验证架构决策的最后一步也是最大概率被跳过的一步是为当前方案设定失效信号。比如选择单体失效信号可以是“发布一个常规需求需要超过半天”或者“跨模块改动占比持续上升”。选择微服务失效信号可以是“一次跨服务排查平均超过半天”或者“服务数量在增长但独立交付能力并没有提升”。这些信号最好在架构评审当天就写下来放进项目文档。它相当于给架构装了一个仪表盘等到指标亮了再启动下一次演进而不是靠某个人忽然拍脑袋说“我们该重构了”。我个人的体会是架构不是一次性的考试而是一个持续决策的过程。每次评审与其说是在选架构不如说是在确认当前阶段的问题和约束。把“为什么要这样做”想清楚比“现在用什么架构”更能保护半年后的自己。这里再补一个我坚持了很久的小习惯每次架构决策记录至少写出三条备选方案哪怕其中两条明显不靠谱也要写下排除理由。这个方法看着繁琐却能在关键时候救场——当有人质疑决策时你能给出完整思考链路而不是一句“当时大家都这么定”。架构这件事真正值钱的部分从来不是最终那张图而是从约束到结论之间那段可追溯的推理过程。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑