一,认识微服务
1.1 单体架构
单体架构(monolithic structure):顾名思义,整个项目中所有功能模块都在一个工程中开发;项目部署时需要对所有模块一起编译、打包;项目的架构设计、开发模式都非常简单。

当项目规模较小时,这种模式上手快,部署、运维也都很方便,因此早期很多小型项目都采用这种模式。
但随着项目的业务规模越来越大,团队开发人员也不断增加,单体架构就呈现出越来越多的问题:
- 团队协作成本高:试想一下,你们团队数十个人同时协作开发同一个项目,由于所有模块都在一个项目中,不同模块的代码之间物理边界越来越模糊。最终要把功能合并到一个分支,你绝对会陷入到解决冲突的泥潭之中。
- 系统发布效率低:任何模块变更都需要发布整个系统,而系统发布过程中需要多个模块之间制约较多,需要对比各种文件,任何一处出现问题都会导致发布失败,往往一次发布需要数十分钟甚至数小时。
- 系统可用性差:单体架构各个功能模块是作为一个服务部署,相互之间会互相影响,一些热点功能会耗尽系统资源,导致其它服务低可用。
1.2 微服务
微服务架构,首先是服务化,就是将单体架构中的功能模块从单体应用中拆分出来,独立部署为多个服务。同时要满足下面的一些特点:
- 单一职责:一个微服务负责一部分业务功能,并且其核心数据不依赖于其它模块。
- 团队自治:每个微服务都有自己独立的开发、测试、发布、运维人员,团队人员规模不超过10人
- 服务自治:每个微服务都独立打包部署,访问自己独立的数据库。并且要做好服务隔离,避免对其它服务产生影响
例如,示例商城项目,我们就可以把商品、用户、购物车、交易等模块拆分,交给不同的团队去开发,并独立部署:

那么,单体架构存在的问题有没有解决呢?
- 团队协作成本高?
- 由于服务拆分,每个服务代码量大大减少,参与开发的后台人员在1~3名,协作成本大大降低
- 系统发布效率低?
- 每个服务都是独立部署,当有某个服务有代码变更时,只需要打包部署该服务即可
- 系统可用性差?
- 每个服务独立部署,并且做好服务隔离,使用自己的服务器资源,不会影响到其它服务。
综上所述,微服务架构解决了单体架构存在的问题,特别适合大型互联网项目的开发,因此被各大互联网公司普遍采用。大家以前可能听说过分布式架构,分布式就是服务拆分的过程,其实微服务架构正式分布式架构的一种最佳实践的方案。
当然,微服务架构虽然能解决单体架构的各种问题,但在拆分的过程中,还会面临很多其它问题。比如:
- 如果出现跨服务的业务该如何处理?
- 页面请求到底该访问哪个服务?
- 如何实现各个服务之间的服务隔离?
1.3 SpringCloud
微服务拆分以后碰到的各种问题都有对应的解决方案和微服务组件,而 SpringCloud 框架可以说是目前 Java 领域最全面的微服务组件的集合了。Spring Cloud

而且 SpringCloud 依托于 SpringBoot 的自动装配能力,大大降低了其项目搭建、组件使用的成本。对于没有自研微服务组件能力的中小型企业,使用 SpringCloud 全家桶来实现微服务开发可以说是最合适的选择了!
二,微服务拆分
服务拆分一定要考虑几个问题:
- 什么时候拆?
- 如何拆?
2.1 什么时候拆
一般情况下,对于一个初创的项目,首先要做的是验证项目的可行性。因此这一阶段的首要任务是敏捷开发,快速产出生产可用的产品,投入市场做验证。为了达成这一目的,该阶段项目架构往往会比较简单,很多情况下会直接采用单体架构,这样开发成本比较低,可以快速产出结果,一旦发现项目不符合市场,损失较小。
如果这一阶段采用复杂的微服务架构,投入大量的人力和时间成本用于架构设计,最终发现产品不符合市场需求,等于全部做了无用功。
所以,对于大多数小型项目来说,一般是先采用单体架构,随着用户规模扩大、业务复杂后再逐渐拆分为 微服务架构。这样初期成本会比较低,可以快速试错。但是,这么做的问题就在于后期做服务拆分时,可能会遇到很多代码耦合带来的问题,拆分比较困难(前易后难)。
而对于一些大型项目,在立项之初目的就很明确,为了长远考虑,在架构设计时就直接选择微服务架构。虽然前期投入较多,但后期就少了拆分服务的烦恼(前难后易)。
2.2 怎么拆
之前我们说过,微服务拆分时粒度要小,这其实是拆分的目标。具体可以从两个角度来分析:
- 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
- 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖,或者依赖接口的稳定性要强。
高内聚首先是单一职责, 但不能说一个微服务就一个接口,而是要保证微服务内部业务的完整性为前提。目标是当我们要修改某个业务时,最好就只修改当前微服务,这样变更的成本更低。
一旦微服务做到了高内聚,那么服务之间的耦合度自然就降低了。
当然,微服务之间不可避免的会有或多或少的业务交互,比如下单时需要查询商品数据。这个时候我们不能在订单服务直接查询商品数据库,否则就导致了数据耦合。而应该由商品服务对应暴露接口,并且一定要保证微服务对外接口的稳定性(即:尽量保证接口外观不变)。虽然出现了服务间调用,但此时无论你如何在商品服务做内部修改,都不会影响到订单微服务,服务间的耦合度就降低了。
明确了拆分目标,接下来就是拆分方式了。我们在做服务拆分时一般有两种方式:
-
纵向拆分
所谓纵向拆分,就是按照项目的功能模块来拆分。例如示例商城中,就有用户管理功能、订单管理功能、购物车功能、商品管理功能、支付功能等。那么按照功能模块将他们拆分为一个个服务,就属于纵向拆分。这种拆分模式可以尽可能提高服务的内聚性。
-
横向拆分
而横向拆分,是看各个功能模块之间有没有公共的业务部分,如果有将其抽取出来作为通用服务。例如用户登录是需要发送消息通知,记录风控数据,下单时也要发送短信,记录风控数据。因此消息发送、风控数据记录就是通用的业务功能,因此可以将他们分别抽取为公共服务:消息中心服务、风控管理服务。这样可以提高业务的复用性,避免重复开发。同时通用业务一般接口稳定性较强,也不会使服务之间过分耦合。
当然,由于示例商城并不是一个完整的项目,其中的短信发送、风控管理并没有实现,这里就不再考虑了。而其它的业务按照纵向拆分,可以分为以下几个微服务
- 用户服务
- 商品服务
- 订单服务
- 购物车服务
- 支付服务
总结:
- 按照业务模块来拆分
- 抽取公共服务,提高复用性
2.3 工程结构
工程结构有两种:
独立 Project:每一个服务是一个 Project,用一个文件夹存放这些服务。\
结构示例
├── user-service/ # 独立工程(Git仓库)
│ ├── src/ # 代码目录
│ └── pom.xml # 独立构建文件
├── order-service/ # 独立工程
│ ├── src/
│ └── pom.xml
└── gateway/ # 独立工程
├── src/
└── pom.xml
优点:
- 完全解耦:每个服务独立仓库,物理隔离,避免误改其他服务代码。
- 独立部署:CI/CD流水线可单独构建、测试、发布(适合大型团队分工)。
- 权限控制:可针对不同仓库设置不同的开发者权限。
缺点:
- 依赖管理困难:公共依赖(如工具类、Feign接口)需手动同步版本。
- 开发环境复杂:需克隆多个仓库,本地启动多个服务。
- 跨服务调试麻烦:需手动切换工程。
适用场景:
- 团队规模大,服务由不同小组维护。
- 服务生命周期差异大(如某些服务频繁迭代,其他服务稳定)。
- 需要严格的代码权限隔离(如金融、政务系统)。
Maven 聚合:整个微服务项目是一个 Project,下面每一个服务是一个 model
结构示例
spring-cloud-project/ # 父工程(Git仓库)
├── pom.xml # 聚合所有子模块
├── common/ # 公共模块
│ └── pom.xml
├── user-service/ # 子模块
│ ├── src/
│ └── pom.xml
├── order-service/ # 子模块
│ ├── src/
│ └── pom.xml
└── gateway/ # 子模块
├── src/
└── pom.xml
优点:
- 统一依赖管理:父POM统一定义版本,避免冲突。
- 代码共享便捷:公共代码(如DTO、Utils)放在
common模块,自动同步。 - 开发调试高效:一键启动所有依赖服务(配合
docker-compose)。 - 重构安全:IDE可全局分析跨服务调用(如Feign接口)。
缺点:
- 耦合风险:开发者可能误改其他模块代码。
- 构建耗时:全量构建时需编译所有模块(可通过
mvn -pl指定模块解决)。
适用场景:
- 中小团队,服务关联紧密。
- 快速迭代的全栈项目(如创业公司MVP)。
- 需要频繁跨服务联调的场景。
2.4 Maven聚合方式服务拆分
- 在项目中创建module
- 选择maven模块,并设定JDK版本:
- 商品模块,我们起名为
item-service: - 引入依赖
- 即这个项目启动所需要的依赖pom文件
- 编写启动类
- 即这个服务单独的Application启动类
- 编写是配置文件
- 可以从原单体项目中拷贝然后修改
- 然后拷贝原单体项目中拷贝中与商品管理有关的代码到
item-service - 将所需要的数据库表新建一个MySQL实例进行存放
- 启动测试
总结:就是将要拆分的模块的代码,配置文件,启动类,数据库数据全部都拆到一个单独的新模块里面即可。
2.5 服务调用
服务拆分之后,一个模块需要使用到另一个模块的数据,可以向前端那样,基于 http 请求,去获取另一个模块的数据。
常见技术:
- HTTP/REST:通过
RestTemplate、OpenFeign(Spring Cloud)或Apache HttpClient。 - RPC:如 Dubbo、gRPC、Thrift。
适用场景:需要即时响应的操作(如查询订单详情)。
- 优缺点:
- ✅ 简单直观,符合人类思维。
- ❌ 调用链过长会导致延迟叠加(需优化或改用异步)。
| 对比项 | RPC 框架 | HTTP/REST |
|---|---|---|
| 抽象层级 | 方法调用(像调用本地函数一样) | 资源操作(基于 URI 和 HTTP 动词) |
| 目标 | 隐藏网络细节,专注业务逻辑 | 标准化、无状态的资源交互 |
| 典型代表 | Dubbo、gRPC、Thrift | Spring Cloud OpenFeign、RestTemplate |
RestTemplate 示例
private void handleCartItems(List<CartVO> vos) {
// TODO 1.获取商品id
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 2.查询商品
// List<ItemDTO> items = itemService.queryItemByIds(itemIds);
// 2.1.利用RestTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}", //url
HttpMethod.GET, //方法
null, //请求体
new ParameterizedTypeReference<List<ItemDTO>>() { //返回值类型
},
Map.of("ids", CollUtil.join(itemIds, ",")) //请求参数
);
// 2.2.解析响应
if(!response.getStatusCode().is2xxSuccessful()){
// 查询失败,直接结束
return;
}
List<ItemDTO> items = response.getBody();
if (CollUtils.isEmpty(items)) {
return;
}
// 3.转为 id 到 item的map
Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
// 4.写入vo
for (CartVO v : vos) {
ItemDTO item = itemMap.get(v.getItemId());
if (item == null) {
continue;
}
v.setNewPrice(item.getPrice());
v.setStatus(item.getStatus());
v.setStock(item.getStock());
}
}
拆分微服务
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。