目录
[1. 认识微服务](#1. 认识微服务)
[1.1. 单体架构](#1.1. 单体架构)
[1.2. 微服务](#1.2. 微服务)
[1.3. Spring Cloud](#1.3. Spring Cloud)
[2. 微服务拆分](#2. 微服务拆分)
[2.1. 服务拆分原则](#2.1. 服务拆分原则)
[2.1.1. 什么时候拆](#2.1.1. 什么时候拆)
[2.1.2. 怎么拆](#2.1.2. 怎么拆)
[2.2. 拆分购物车、商品服务](#2.2. 拆分购物车、商品服务)
[2.2.1. 创建商品服务](#2.2.1. 创建商品服务)
[2.2.2. 创建购物车服务](#2.2.2. 创建购物车服务)
[2.3. 服务调用](#2.3. 服务调用)
[2.3.1. RestTemplate](#2.3.1. RestTemplate)
[2.3.2. 远程调用](#2.3.2. 远程调用)
[2.4. 总结](#2.4. 总结)
本文介绍了微服务架构的核心概念、拆分原则及实践方法。首先对比了单体架构与微服务架构的优缺点,指出微服务通过拆分功能模块为独立服务,解决了团队协作、发布效率和系统可用性问题。文章详细讲解了微服务的拆分时机(初创项目先单体后拆分,大型项目直接采用)和拆分原则(高内聚、低耦合),并以电商系统为例演示了商品和购物车服务的拆分过程。最后通过RestTemplate实现服务间HTTP调用,展示了微服务间通信的基本方式。文章还预告了后续将介绍服务注册发现和OpenFeign远程调用技术。
前言
在互联网时代,越来越多的一线互联网公司都在使用微服务技术。

那么:
-
到底什么是微服务?
-
企业该不该引入微服务?
-
微服务技术该如何在企业落地?
本篇文章带你揭开微服务的神秘面纱。
1. 认识微服务
1.1. 单体架构
单体架构(Monolithic Structure):顾名思义,整个项目中所有功能模块都在一个工程中开发;项目部署时需要对所有模块一起编译、打包;项目的架构设计、开发模式都非常简单。

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

例如,一个电商项目,我们可以把商品、用户、购物车、交易等模块拆分,交给不同的团队去开发,并独立部署。
那么,单体架构存在的问题有没有解决呢?
-
团队协作成本高?
- 由于服务拆分,每个服务代码量大大减少,协作成本大大降低
-
系统发布效率低?
- 每个服务都是独立部署,当有某个服务有代码变更时,只需要打包部署该服务即可
-
系统可用性差?
- 每个服务独立部署,并且做好服务隔离,使用自己的服务器资源,不会影响到其它服务。
微服务架构解决了单体架构存在的问题,特别适合大型互联网项目的开发,因此被各大互联网公司普遍采用。分布式就是服务拆分的过程,微服务架构是分布式架构的一种最佳实践方案。
当然,微服务架构虽然能解决单体架构的各种问题,但在拆分的过程中,还会面临很多其它问题:
-
如果出现跨服务的业务该如何处理?
-
页面请求到底该访问哪个服务?
-
如何实现各个服务之间的服务隔离?
这些问题,我们在后续的学习中会给大家逐一解答。
1.3. Spring Cloud
微服务拆分以后碰到的各种问题都有对应的解决方案和微服务组件,而 Spring Cloud 框架可以说是目前 Java 领域最全面的微服务组件的集合了。

而且 Spring Cloud 依托于 Spring Boot 的自动装配能力,大大降低了其项目搭建、组件使用的成本。
官网地址:Spring Cloud
Spring Cloud 版本对应关系:
| Spring Cloud 版本 | Spring Boot 版本 |
|---|---|
| 2022.0.x (Kilburn) | 3.0.x |
| 2021.0.x (Jubilee) | 2.6.x, 2.7.x |
| 2020.0.x (Ilford) | 2.4.x, 2.5.x |
| Hoxton | 2.2.x, 2.3.x |
| Greenwich | 2.1.x |
| Finchley | 2.0.x |
推荐使用次新版本:Spring Cloud 2021.0.x 以及 Spring Boot 2.7.x 版本。
另外,Alibaba 的微服务产品 Spring Cloud Alibaba 目前也成为了 Spring Cloud 组件中的一员。
2. 微服务拆分
2.1. 服务拆分原则
服务拆分一定要考虑几个问题:
-
什么时候拆?
-
如何拆?
2.1.1. 什么时候拆
一般情况下,对于一个初创的项目,首先要做的是验证项目的可行性。因此这一阶段的首要任务是敏捷开发,快速产出生产可用的产品,投入市场做验证。
所以,对于大多数小型项目来说,一般是先采用单体架构,随着用户规模扩大、业务复杂后再逐渐拆分为微服务架构。这样初期成本会比较低,可以快速试错。
而对于一些大型项目,在立项之初目的就很明确,为了长远考虑,在架构设计时就直接选择微服务架构。
2.1.2. 怎么拆
微服务拆分时粒度要小,具体可以从两个角度来分析:
-
高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
-
低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖,或者依赖接口的稳定性要强。
明确了拆分目标,接下来就是拆分方式了。我们在做服务拆分时一般有两种方式:
-
纵向拆分:按照项目的功能模块来拆分。例如用户管理功能、订单管理功能、购物车功能、商品管理功能、支付功能等。这种拆分模式可以尽可能提高服务的内聚性。
-
横向拆分:看各个功能模块之间有没有公共的业务部分,如果有将其抽取出来作为通用服务。例如消息发送、风控数据记录就是通用的业务功能,可以抽取为公共服务。
按照纵向拆分,可以分为以下几个微服务:
-
用户服务
-
商品服务
-
订单服务
-
购物车服务
-
支付服务
2.2. 拆分购物车、商品服务
接下来,我们先把商品管理功能、购物车功能抽取为两个独立服务。
一般微服务项目有两种不同的工程结构:
-
完全解耦:每一个微服务都创建为一个独立的工程,甚至可以使用不同的开发语言来开发,项目完全解耦。
-
优点:服务之间耦合度低
-
缺点:每个项目都有自己的独立仓库,管理起来比较麻烦
-
-
Maven 聚合:整个项目为一个 Project,然后每个微服务是其中的一个 Module
-
优点:项目代码集中,管理和运维方便
-
缺点:服务之间耦合,编译时间较长
-
2.2.1. 创建商品服务
创建 Maven 模块 item-service后
引入依赖:
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo-parent</artifactId>
<groupId>com.example</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>item-service</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!--common-->
<dependency>
<groupId>com.example</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类:
java
package com.example.item;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.example.item.mapper")
@SpringBootApplication
public class ItemApplication {
public static void main(String[] args) {
SpringApplication.run(ItemApplication.class, args);
}
}
配置文件 application.yaml:
java
server:
port: 8081
spring:
application:
name: item-service
profiles:
active: dev
datasource:
url: jdbc:mysql://${db.host}:3306/hm-item?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: ${db.pw}
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
update-strategy: not_null
id-type: auto
logging:
level:
com.example: debug
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
knife4j:
enable: true
openapi:
title: 商品服务接口文档
description: "商品服务接口文档"
email: dev@example.com
concat: 开发者
url: https://www.example.com
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.example.item.controller
2.2.2. 创建购物车服务
创建 Maven 模块 cart-service:
启动类:
java
package com.example.cart;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.example.cart.mapper")
@SpringBootApplication
public class CartApplication {
public static void main(String[] args) {
SpringApplication.run(CartApplication.class, args);
}
}
配置文件 application.yaml:
java
server:
port: 8082
spring:
application:
name: cart-service
profiles:
active: dev
datasource:
url: jdbc:mysql://${db.host}:3306/hm-cart?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: ${db.pw}
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
update-strategy: not_null
id-type: auto
logging:
level:
com.example: debug
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
knife4j:
enable: true
openapi:
title: 购物车服务接口文档
description: "购物车服务接口文档"
email: dev@example.com
concat: 开发者
url: https://www.example.com
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.example.cart.controller
2.3. 服务调用
在拆分的时候,我们发现一个问题:购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了 item-service 服务,导致无法查询。
因此,现在查询购物车列表的流程变成了这样:

那么问题来了:我们该如何在 cart-service 中实现对 item-service 服务的查询呢?
答案是通过 HTTP 请求的方式来完成的,不仅仅可以实现远程查询,还可以实现新增、删除等各种远程请求。
2.3.1. RestTemplate
Spring 给我们提供了一个 RestTemplate 的 API,可以方便的实现 Http 请求的发送。
注册 RestTemplate:
java
package com.example.cart.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RemoteCallConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2.3.2. 远程调用
修改 CartServiceImpl 的 handleCartItems 方法:
java
private void handleCartItems(List<CartVO> vos) {
// 1.获取商品id
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 2.查询商品
// 2.1.利用RestTemplate发起http请求
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",
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());
}
}
其中 item-service 提供查询接口,cart-service 利用 Http 请求调用该接口。因此 item-service 可以称为服务的提供者,而 cart-service 则称为服务的消费者或服务调用者。
2.4. 总结
什么时候需要拆分微服务?
-
如果是创业型公司,最好先用单体架构快速迭代开发,验证市场运作模型,快速试错。当业务规模扩大、人员规模增加后,再考虑拆分微服务。
-
如果是大型企业,有充足的资源,可以在项目开始之初就搭建微服务架构。
如何拆分?
-
首先要做到高内聚、低耦合
-
从拆分方式来说,有横向拆分和纵向拆分两种。纵向就是按照业务功能模块,横向则是拆分通用性业务,提高复用性
服务拆分之后,不可避免的会出现跨微服务的业务,此时微服务之间就需要进行远程调用。微服务之间的远程调用被称为 RPC(Remote Procedure Call)。
Java 发送 http 请求可以使用 Spring 提供的 RestTemplate:
-
注册 RestTemplate 到 Spring 容器
-
调用 RestTemplate 的 API 发送请求
-
getForObject:发送 Get 请求并返回指定类型对象 -
PostForObject:发送 Post 请求并返回指定类型对象 -
put:发送 PUT 请求 -
delete:发送 Delete 请求 -
exchange:发送任意类型请求,返回 ResponseEntity
-
下一篇将介绍:服务注册与发现(Nacos)和远程调用(OpenFeign)