☰
Spring Boot架构选型:单体、多模块与微服务核心区别及演进路径
2026/9/30 7:41:47 网站建设 项目流程

在 Spring Boot 相关的话题下,单体、多模块和微服务是绕不开的三个基础形态。放在一起对比的时候,很多人第一反应是"单体就是所有代码扔在一个工程里,多模块就是拆成几个子模块,微服务就是每个功能单独部署",但这种理解只停留在目录结构层面,没有触及架构的本质。这篇梳理想把三个形态的核心区别、演进逻辑和落地代价讲透,尤其是从单体到多模块,再到微服务这条路上,每一步到底解决了什么问题,又引入了什么新麻烦。


1. 单体架构:一切从"能跑起来"开始

1.1 单体的真实定义

单体架构指的是一个应用以单一可部署单元的形式存在。简单说,所有的 Controller、Service、Mapper、实体类、配置文件和静态资源,最终打成一个 JAR 包或 WAR 包,运行在一个 Java 进程里。

很多教程把单体叫 Monolithic,这个词听起来有点贬义,但它其实是绝大多数项目的起点。你新建一个 Spring Boot 工程,写几个接口,连一个数据库,打一个包扔到服务器上跑起来——这就是典型的单体。对于创业初期、业务逻辑还不复杂、用户量还在爬坡阶段的系统,单体是完全合理的形态。

一个典型的单体应用内部会有清晰的逻辑分层:

  • Controller 层:接收 HTTP 请求,做参数校验
  • Service 层:处理业务规则
  • DAO/Mapper 层:访问数据库
  • 领域模型层:Entity、DTO、VO

分层这件事在单体里就已经很重要了。很多单体项目烂就烂在分层混乱,Controller 里直接拼 SQL、Service 里写大段事务逻辑、实体类被各路代码随意修改。这些问题和架构形态无关,和写代码的人有关。

1.2 单体最容易被忽略的优势

讨论单体劣势的文章太多了,但如果你真的做架构决策,必须清楚单体不可替代的优势是什么。

第一,事务处理简单可靠。单体应用的所有业务操作都在同一个进程、同一个数据库连接源上,Spring 的@Transactional可以横跨多个 Service 方法,保证强一致性的成本极低。而到了微服务,跨服务事务要靠分布式事务方案,Seata、TCC、Saga 这些方案的复杂度比业务逻辑本身还高。

第二,调试联调成本低。本地起一个应用,断点随便打,接口调用链路就是方法调用链路,日志都在同一个文件里灌着。不像微服务那样,一次请求穿过四五个服务,日志散落在不同容器里,排查问题要先把 traceId 串起来。

第三,部署运维极其简单。一个包、一条启动命令、一个端口,不需要考虑服务注册、负载均衡、配置中心这些基础设施。对于团队只有几个人、服务器就一两台的场景,单体就是最优解。

1.3 单体什么时候开始"疼"

单体的痛不是"工程越来越大"这种模糊的感觉,而是几个非常具体的信号:

  • 编译时间失控:一个几百万行的单体项目,本地启动可能要两三分钟,全量编译打包要五分钟以上。每次改一行代码,验证逻辑的周期被拉得很长。
  • 代码冲突频繁:几十个人在一个工程里开发,改同一个文件是常态。尤其是配置文件和公共模块,经常出现你合代码时发现别人改了同一个地方。
  • 发布粒度粗:哪怕只改了一个接口的逻辑,也要把整个应用重新打包发布。发布窗口被延长,回滚范围也被放大。
  • 模块边界靠自觉:订单模块的 Service 可以随意调用用户模块的 Mapper,因为代码层面根本没有围墙。时间一长,模块之间的依赖关系就变成一张乱网,谁也说不清哪些逻辑是什么原因放在这里的。

这些信号出现之后,你要做的第一件事未必是上微服务,更合理的路径通常是先做多模块拆分。


2. 多模块架构:在单体的壳里做物理隔离

2.1 多模块到底拆的是什么

多模块架构在 Maven 的项目模型下很好理解:一个父 POM 管着多个子模块,每个子模块是一个独立的 Maven 工程,有自己的groupId、artifactId和职责边界,但最终它们还是会作为一个整体应用被打包运行。

一个典型的 Spring Boot 多模块工程长这样:

project-root/ ├── pom.xml // 父 POM,统一管理依赖版本 ├── common/ // 公共工具模块,不依赖业务 │ ├── pom.xml │ └── src/main/java/... // 通用工具类、异常定义、基础响应封装 ├── domain/ // 领域模型模块 │ ├── pom.xml │ └── src/main/java/... // Entity、枚举、领域服务 ├── repository/ // 数据访问模块 │ ├── pom.xml │ └── src/main/java/... // Mapper、Repository 接口 ├── service/ // 业务逻辑模块 │ ├── pom.xml │ └── src/main/java/... // Service 接口和实现 └── web/ // Web 接入模块,最终启动入口 ├── pom.xml └── src/main/java/... // Controller、配置类

模块之间的依赖关系有一个总原则:依赖方向只能从上层指向上层依赖的更低层,不能反向循环。web 依赖 service,service 依赖 repository,repository 依赖 domain,common 被所有模块共享。谁要是写了 service 反向依赖 web,或者两个模块循环依赖,Maven 在编译期就会给出明确错误,这就是多模块相比"包名隔离"最本质的优势——边界由编译器强制保证。

2.2 多模块不是微服务的缩略版

很多文章倾向于把多模块描述成"微服务的前奏",好像只要把模块拆好了,以后切成微服务就很轻松。这个说法有启发作用,但也容易误导人。

多模块和微服务的区别,不只体现在部署形态上,更体现在运行边界和失败隔离上。多模块再怎么拆,最终仍是同一个 JVM 进程。一个模块里的OutOfMemoryError会导致整个应用挂掉,一个模块里死循环的线程会拖垮所有接口。而微服务把这种风险物理隔离开了:订单服务内存溢出不至于把用户服务的进程也带崩。

所以,多模块架构实际上是把代码层面的"混乱"治理好,但它没有解决运行层面的"风险"。理解了这一点,你才会明白多模块适合解决什么问题,不适合解决什么问题。

多模块真正擅长解决的是:

  • 工程结构混乱,依赖关系不清晰时,用编译器的力量把边界固化下来
  • 多个业务团队同时在一套代码库里开发,需要降低代码冲突概率时,通过模块归属划分各自的地盘
  • 公共基础代码被多个上层模块复用时,把 common 模块独立出来单独维护

它不擅长解决的是:

  • 某个模块成为性能瓶颈,需要单独扩容时(因为整包部署,扩容就是全部模块一起扩)
  • 某个模块崩溃导致全站不可用时(因为没有故障隔离)
  • 不同模块对数据库、中间件的版本要求冲突时(因为最终还在同一应用里跑着)

2.3 多模块拆分中最容易踩的三个坑

第一个坑是拆得太碎。有人为了体现"模块化设计"的功力,把工程拆成十几个模块,每个模块只有两三个类。结果是模块之间的依赖链变长,构建顺序复杂,开发时切来切去很烦躁。正确的拆分粒度应该是:每一个模块都有明确的、独立于其他模块的存在理由,比如"这些类是公共的""这些类专门做数据访问""这些类属于订单业务域"。

第二个坑是模块间依赖失控而没有治理手段。Maven 的依赖检查是编译期保证的,但如果你用了 JAR 包层面的循环依赖,Maven 会直接构建失败干掉你。有人为了绕过这个问题,用反射、用 Map 结构、用 ApplicationContext 手动取值,这等于把编译期的边界保护手动拆掉了。多模块的意义就是让编译器当警察,你不要自己给警察放假。

第三个坑是硬套分层导致模块职责混乱。不是所有项目都得拆出 domain、repository、service、web 四个固定模块。如果你的工程本身就很小,common 加别的全塞一个模块也完全没问题。多模块的拆分策略应该跟着业务边界走,而不是跟着教科书的分层模板走。


3. 微服务架构:真正的分界点是数据库

3.1 微服务的本质特征

微服务的定义有很多种说法,但落到工程上,有几个特征是判别性的。

微服务的每个服务是一个独立的进程,通过 HTTP、gRPC 或消息队列通信;每个服务可以独立开发、独立部署、独立扩容;每个服务原则上拥有自己独立的数据存储或至少独立的数据库 Schema;每个服务由独立的团队(或者至少是子团队)负责。

在这些特征里,最容易引起争论也最关键的是数据库边界。

很多所谓的微服务项目,其实是"微服务外壳 + 单体数据库"。服务确实拆开了,部署也是独立部署了,但所有服务都连同一个 MySQL 实例,甚至共享同一套表结构。订单服务可以直接写用户服务的表,共享库存服务的表。这种架构带来的结果往往是:表面上是微服务,实际上还是单体,而且比单体更复杂——因为它多出了一堆服务间通信和链路追踪的代码,却依然没有摆脱模块紧耦合的数据库层。

真正的微服务架构里,一个服务对另一个服务的数据只能通过对方提供的接口获取,不能直接访问对方的数据库。订单服务想确认用户是否存在,应该调用用户服务的接口(或同步一份只读数据),而不是自己连到用户库去查。这个约束是保证"服务自主性"的关键,做不到这一点,你就没有真正进入微服务的领域。

3.2 服务拆分的依据

服务怎么拆从来都是一个架构难题。一个常见的指导原则是围绕业务能力拆,比如电商系统拆为订单服务、商品服务、库存服务、用户服务、支付服务、物流服务等。每个服务对应一个清晰的业务域,有自己的领域模型和数据库。

更专业一点的做法是使用DDD(领域驱动设计)中的限界上下文来划定服务边界。以用户为例,"用户注册"是一个上下文,"用户登录"是一个上下文,但从业务完整性和数据一致性角度考虑,它们通常会放在同一个服务里。判断依据是:如果一个操作需要同时修改多张表且这些表分布在不同服务中,就会引入跨服务事务问题——这通常说明边界划错了。

除了业务能力,拆服务的辅助判断标准还包括:

  • 独立生命周期:某段逻辑的发布频率明显高于其他部分,值得独立部署
  • 独立性能需求:某段逻辑对资源的消耗特别大,需要单独扩容
  • 独立团队承接:有明确的团队负责,可以承载完整的研发和运维周期
  • 故障隔离需求:某段逻辑挂了不能影响主链路

3.3 微服务的分布式代价

这一点是很多从单体转微服务的团队低估最严重的地方。

单体里一个方法调用搞定的事,到了微服务变成三次远程调用;局部事务变成分布式事务;本地日志变成需要串联的分布式链路;服务之间互相调用需要处理超时、重试、熔断和降级;多个服务实例需要服务注册与发现、负载均衡;灰度发布需要流量治理。

列举几个具体的代价:

  • 通信开销:一次接口请求从单体里的几个毫秒,变成微服务链路里几十甚至上百毫秒。对低延迟系统而言,这个差距是致命的。
  • 一致性问题:单体里跨表更新靠一个本地事务搞定,微服务里跨服务更新基本只能追求最终一致性。用户下单扣库存这一步,要么做分布式事务牺牲吞吐量,要么做事务消息引入更大复杂度。
  • 部署和运维复杂度:一个单体应用你可能只需要 CI/CD 里配一条流水线,微服务架构通常需要服务网关、注册中心(Nacos/Consul)、配置中心、链路追踪(SkyWalking/Zipkin)、容器编排(K8s)、可观测平台,这一整套基础组件的学习成本和维护成本远超业务代码本身。

所以微服务从来不是一个"上了就高级"的技术选择,而是一个"业务复杂度已经大到不拆不行"的必然结果。


4. 选择合适的架构:判断标准和实践经验

4.1 什么情况应该坚持单体

我是很支持大部分团队坚持单体的。不是因为单体好写,而是因为单体能承载的量级远比想象中高。

一个配置得当、代码治理良好的单体应用,支撑日活几万到几十万的业务量是完全可能的。你需要做的不是换成微服务,而是把单体的性能瓶颈逐个击破——引入缓存、异步化、分库分表、读写分离。这些技术手段和单体架构不冲突。

如果团队规模不到 20 人,业务还在验证阶段,服务端的职责主要是 CRUD 加上有限的复杂业务逻辑,这种情况下上微服务的收益几乎为零,代价却不小。真正该做的是花时间把单体结构的层次理清,把核心业务链路写清楚,把数据库模型设计合理。

4.2 什么情况适合多模块

当单体的痛点开始具体化时,第一步就是做模块化改造。这里的判断标准是:

  • 代码库规模开始超过一个开发者的脑容量(大概 10 万行以上)
  • 多人协作时,分支合并冲突频繁
  • 团队需要并行开发不同领域,但互相之间的接口还没稳定下来

多模块改造可以在不改变部署方式的前提下,把依赖关系理清楚、让团队能沿着模块边界分工。很多系统走到多模块这一步就能健康运行很久,不一定非要继续拆成微服务。

4.3 什么情况下真的该上微服务

满足下面这些条件,上微服务才具备合理性:

  1. 业务模块之间已经出现明确的性能差异,比如某些模块 CPU 密集、某些模块 IO 密集,放在一个进程里互相拖累;
  2. 部署频率差异明显,比如核心订单模块每周发布两三次,而报表模块一个月发布一次,单体化部署被迫以最频繁的模块为整体流水线节奏;
  3. 团队规模已经达到可以按服务自治的水平,每个服务有独立的负责人和运维能力;
  4. 基础设施已经成熟,Docker、K8s、CI/CD、链路追踪组件已经被团队掌握,而不是技术选型上还没谱就开始拆。

4.4 表格式对比:三种架构的决策坐标系

维度单体架构多模块架构微服务架构
部署形态一个应用、一个进程一个应用、一个进程、多个模块多个应用、多个进程
模块边界靠包名和自觉编译器强制进程网络边界强制
代码规模承载小到中,适合早期中到较大,适合成长中大,适合成熟业务和组织
数据库共享一个库一个库每个服务独立库(理想)
事务保障本地事务简单可靠本地事务,仍然简单分布式事务,复杂度高
部署运维极其简单简单复杂,需要基础设施支撑
故障隔离无无有,服务之间互相隔离
关键成本代码耦合风险模块依赖治理通信、运维、数据一致性

这张表不是要告诉你哪种架构"最厉害",而是要帮助你根据团队所处的阶段,找到投入产出比最高的那个选项。


5. 从单体走向微服务的实用路径

5.1 从代码层开始:模块化做扎实

如果你最终目标是微服务,那就不要跳过模块化这一步。模块化改造可以先行,步骤如下:

  1. 梳理现有代码,按照业务域划分候选模块;
  2. 梳理模块之间的依赖关系,绘制依赖图;
  3. 剔除反向依赖和循环依赖;
  4. 将各业务域的数据访问代码隔离,禁止跨域直接访问对方表;
  5. 对外提供独立的 Service 接口,最好为将来 HTTP 化预留清爽的接口语义。

这一步的目标不是变成微服务,而是让代码可以在将来被"切"出来。一个模块如果连编译期的依赖都没理清,拆成独立服务时也会在远程调用之间把这种混乱放大。

5.2 从数据层开始:数据库先拆

微服务改造中最难、最不能绕过的就是数据库拆分。这一步不建议一次性做完。正确姿势是逐步拆:

  • 先做逻辑隔离,拆 Schema 但放同一实例——迁移成本低,但已经可以强制数据边界
  • 再按服务拆物理库,同时把跨服务的数据访问改为 API 调用
  • 处理共享表问题:比如用户表被多个服务共用,就需要考虑用用户服务统一对外提供用户数据访问,其他服务通过调用用户服务接口来获取数据

数据库拆分的核心风险在于分布式事务,一旦拆库,原来一个本地事务能保证的一致性就需要重新设计。这里我的经验是:优先消灭跨服务事务的写法,能通过最终一致性解决的不要引分布式事务框架。分布式事务框架是最后手段,不是默认方案。

5.3 从部署层开始:基础设施先行

微服务改造不是"把模块改成独立服务"就完事了,你还需要链路追踪、服务注册与发现、配置中心、API 网关、容器编排等配套。如果基础设施没准备好就拆服务,你会面对一个无从下手的新体系。

推荐的落地顺序:

  • 先引入 Nacos(或 Consul)做注册中心,让服务可以互相发现
  • 引入 Sentinel(或 Resilience4j)做熔断限流
  • 引入 SkyWalking 做链路追踪,把分布式调用的"黑盒"打开
  • 接入 Docker 和 K8s,把部署流水线标准化
  • 最后才是把业务模块逐个拆为独立服务

5.4 拆服务时的两个进阶建议

第一,先拆边缘服务,再拆核心服务。比如先拆分登录、验证码、消息通知这类边界清晰、核心链路依赖少的服务,跑通整套流程后,再拆订单、支付这类核心链路。边缘服务出问题影响面小,适合做实验。

第二,一个服务的拆分周期不要太长。一个服务拆出来,从代码调整到独立部署,最好控制在一到两周内。时间太长,分支合并的痛苦会稀释你对拆分的信心。


6. 常见误区与面试中的高频问题视角

6.1 四个常见的架构认知误区

误区一:微服务就是"多模块工程把每个模块单独部署"。模块划分和服务划分的粒度、边界、通信方式完全不同,多模块间的调用是本地方法调用,微服务间是网络调用,这个本质区别决定了复杂度不在一个数量级。

误区二:服务拆得越细越好。服务粒度太细会导致调用链路过长,请求延迟增加,而且每个服务都需要独立的运维配置和监控面板,团队的人力会被运维工作大量吞噬。业界更鼓励"大而清晰"的服务边界,而不是"小而碎片化"的疯狂拆分。

误区三:上了微服务就有高可用。微服务只是创造了高可用的结构条件,真正的高可用要靠熔断、限流、降级、重试、幂等、监控告警这六件套支撑起来。很多微服务项目线上照样挂,原因就是基础设施一个没配齐。

误区四:数据库没有边界也可以叫微服务。前面反复提过,共享数据库的微服务是伪微服务,它保留了单体的耦合却引入了分布式的成本,属于最不划算的组合。

6.2 面试题背后的真实考察点

微服务相关面试题在网络上热度很高,但这些题目背后真正考察的不是背概念,而是你是否真动手做过架构权衡。

举个例子,面试官问"微服务如何保证数据一致性",重点不是你能不能背出 Seata 的 AT 模式和 TCC 模式的区别,而是你能不能解释清楚什么场景下需要强一致、什么场景可以接受最终一致,以及你为什么做这个判断。能讲出"我们支付链路用了 TCC,因为资金操作不能异步对账兜底;库存链路用了事务消息,因为允许短暂超卖后补偿"这种回答,才说明你真的搞懂了这三者的差别。

再比如"单体和多模块的区别",考察的核心是:你是否有过从混乱工程走向结构化工程的实际体会,是否知道依赖治理、模块边界、构建生命周期这些问题在真实项目里的表现。

这也就是为什么我建议每个做后端的人,至少完整经历过一次单体 → 多模块 → 微服务的演进过程。演进过程中踩过的坑,比看十遍理论文章都更有价值:你会真切地体会到本地事务有多幸福、早上十分钟启动应用有多奢侈、一个服务宕机会不会拖垮全站,数据拆库时面对 OLTP 报表查询的挣扎……这些体验才是架构判断力的来源。

6.3 一套通用的系统侧写模板

如果你正在做一个架构选型的汇报,建议用下面这套模板把系统现状讲清楚,比空谈概念有用得多:

  • 当前在线用户量、接口 QPS 峰值、数据量级
  • 核心业务链路的调用模式(同步?异步?批处理?)
  • 当前工程结构、代码量、团队人数、发布频率
  • 目前最痛的三件事(性能?冲突?发布风险?)
  • 目标架构需要撑住未来多久的扩张(一年?三年?)
  • 团队对基础设施的熟练程度(Docker?K8s?监控平台?)

把这几项填完,架构选型的答案基本自己就浮现了:最痛的事若只是代码混乱,多模块;最痛的事若是扩容和故障隔离,微服务;若还不到痛的时候,那就先把单体写整洁。


架构演进从来不是"越新越好",而是"越适配越好"。单体是一块扎实的地基,多模块是在地基上砌出边界分明的隔间,微服务则是把隔间变成独立的生产车间。从单体到微服务,中间跨越的不只是代码结构,还有团队协作方式、运维体系和数据管理思想的全面升级。没有一条路是错路,只有"在不合适的时间做了不合适的投资"这一种遗憾。实际工作中,我的建议始终是:认真治理好你现有的单体,判断清楚痛点的真实来源,再决定要不要迈向下一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询