跳到主要内容Spring Cloud 微服务架构核心概念与实战入门 | 极客日志Javajava
Spring Cloud 微服务架构核心概念与实战入门
综述由AI生成Spring Cloud 是构建分布式系统的工具集,基于 Spring Boot 简化微服务开发。文章梳理了从单体到微服务的架构演进,对比了集群与分布式差异,详解了 Spring Cloud 版本规范及主流实现方案(Netflix 与 Alibaba)。通过订单与商品服务的拆分示例,演示了数据库准备、工程搭建及远程调用流程,为后续学习服务发现与治理打下基础。
板砖工程师11 浏览 在学习 Spring Cloud 之前,我们先来了解一下什么是微服务,以及它的发展历史。架构演进过程中遇到了哪些问题?又是如何解决的?Spring Cloud 在其中又扮演了什么角色?
微服务架构的演进
单体架构
早期项目通常将所有功能模块(如用户管理、订单处理)打包在一个应用中,共享同一数据库,模块间通过函数调用直接通信。这种方式开发、测试、部署简单,适合小型项目或初创团队。

优点:
- 开发简单:适合小型项目。
- 部署便捷:只需部署一个应用(如单个 WAR/JAR 文件)。
- 性能高:模块间调用无网络开销(本地调用)。
缺点:
- 难以扩展:只能整体扩展,无法针对模块单独扩容。
- 维护困难:代码库膨胀后牵一发而动全身。
- 技术僵化:难以引入新语言或框架。
- 可靠性差:单点故障可能导致整个系统崩溃。
随着用户量和流量增长,单体应用面临服务器压力大、业务耦合度高、微小问题导致全系统挂掉等风险。此时,我们需要考虑从横向和纵向两个维度进行优化。
集群和分布式架构
横向扩展
添加服务器,将单台机器变成多台机器的集群。通过负载均衡部署相同应用,提高并发能力和可用性。

纵向扩展
将应用按业务拆分,分为多个项目的分布式架构。将系统拆分为不同服务(如订单服务、支付服务),部署在不同节点上,服务间通过 RPC/HTTP 通信。

也可以结合使用,先以单体架构规模的项目为单位进行垂直划分,每个项目再部署多台服务器。

集群和分布式的区别:
- 概念:集群是多个计算机处理相同任务;分布式是多个计算机完成不同任务。
- 功能:集群节点功能相同且可替代;分布式节点业务功能不同。
- 关系:两者常配合使用,实际设计中统称为分布式架构。
优缺点对比:
- 优点:高可用性(避免单点)、可扩展性(水平扩展)、性能提升(并行处理)。
- 缺点:复杂度高(需处理网络、一致性)、运维成本高(需服务发现、监控等)。
当服务越来越多时,重复代码和复杂的调用关系成为痛点。我们将通用的共享服务提取成独立的基础服务,这就形成了微服务。
微服务架构
微服务按业务边界划分独立服务(如用户服务、商品服务),每个服务拥有自己的数据库,可独立开发、部署、扩展,技术栈异构,通常通过 REST/gRPC 或消息队列解耦。
简而言之,微服务就是很小的服务,仅对应单一功能,只做一件事,且可单独部署运行。相比于分布式架构,它拆分粒度更小,更强调服务的专业化和精细分工。
- 敏捷开发:小团队专注单个服务,迭代更快。
- 弹性扩展:可针对热点服务单独扩容。
- 容错性:服务隔离,故障不易扩散。
- 技术多样性:不同服务可用不同技术实现。
- 运维复杂:需容器化、编排、日志聚合。
- 分布式难题:需处理最终一致性、跨服务查询、链路追踪。
- 网络开销:服务间调用延迟高于本地调用。
演化过程大致为:单体架构 → 集群 → 微服务。在这个过程中,虽然微服务优势明显,但服务数量增加带来了新的治理问题。对于 Java 技术栈,Spring Cloud 是目前最成熟的解决方案之一。
Spring Cloud 详解
什么是 Spring Cloud
在微服务架构中,系统被拆分为多个独立服务,随之带来了一系列分布式环境下的挑战。Spring Cloud 针对这些问题提供了标准化解决方案,将解决常见问题的开源框架基于 Spring Cloud 规范进行了整合,并基于 Spring Boot 风格进行了封装,屏蔽了复杂的配置。
| 分布式系统问题 | Spring Cloud 的解决方案 | 对应组件/技术 |
|---|
| 服务发现与注册 | 动态管理服务实例上下线 | Eureka、Nacos、Consul、Zookeeper |
| 服务间调用 | 简化远程调用,支持负载均衡和容错 | OpenFeign、Ribbon、RestTemplate |
| 配置中心 | 统一管理分布式配置,支持动态更新 | Spring Cloud Config、Nacos、Apollo |
| 熔断与容错 | 防止服务雪崩,提升系统弹性 | Hystrix、Sentinel |
| API 网关 | 统一入口、路由、鉴权、限流 | Spring Cloud Gateway、Zuul |
| 分布式事务 | 解决跨服务数据一致性问题 | Seata |
| 消息驱动 | 解耦服务,实现异步通信 | Spring Cloud Stream |
| 链路追踪 | 监控请求链路,定位性能瓶颈 | Sleuth + Zipkin |
Spring Cloud 版本
Spring Cloud 是一个由很多子项目组成的庞大项目,发布阶段不同。为了管理依赖关系及避免冲突,主项目版本命名采用了伦敦地铁站名称(如 Hoxton、Greenwich)。但从 Hoxton 版本之后,版本号变成了 2020.0.0 这样的日期格式。
由于所有子项目都依赖 Spring Boot,两者之间存在严格的对应关系。例如 Spring Boot 3.2.X 对应的 Spring Cloud 版本是 2023.0.X。若版本不匹配,可能出现兼容性问题。
Spring Cloud 实现方案
1. Spring Cloud Netflix
这是 Netflix OSS 在 Spring Cloud 规范下的实现,核心组件包括 Eureka、Ribbon、Hystrix、Zuul 1.x、Feign。但 Netflix 公司在 2018 年左右宣布其核心组件进入维护阶段,Spring Cloud 也删除了这些维护模块,并提供了一些替换建议:
| Netflix 组件 | 替代方案 | 特点 |
|---|
| Ribbon | Spring Cloud LoadBalancer | 支持响应式编程,与 Spring 生态深度集成 |
| Hystrix | Resilience4J | 轻量级,支持函数式编程 |
| Zuul 1.x | Spring Cloud Gateway | 基于 WebFlux 的非阻塞 IO,性能更高 |
2. Spring Cloud Alibaba
这是阿里的开源组件和云产品在 Spring Cloud 规范下的实现,核心组件包括 Nacos、Sentinel、RocketMQ、Seata、Dubbo。相比 Netflix 方案,Alibaba 方案在国内社区更为活跃。
服务拆分实战
了解了基本概念后,我们继续学习如何进行微服务应用开发。第一步是服务拆分。
拆分原则
拆分多小的服务才算微服务没有明确标准,并非越小越好。一般遵循以下原则:
- 单一职责原则(SRP):每个服务只负责一个明确的业务能力,有清晰的定义和边界。
- 服务自治:每个微服务应具备高度自治能力,独立开发、测试、构建、部署、运行。
- 单向依赖:微服务之间做到单向依赖,严禁循环依赖。
简单示例
我们以一个简单的订单系统进行演示,将其拆分为两个服务:订单服务和商品服务。
- 订单服务:提供订单 ID,获取订单详细信息。
- 商品服务:根据商品 ID,返回商品详细信息。
数据准备
根据服务自治原则,每个服务都应该有自己独立的数据库。
工程搭建
父工程创建
创建一个空的 Maven 项目,并只保留 pom.xml。完善 pom 文件:声明父工程打包方式为 pom,使用 properties 进行版本号统一管理,通过 dependencyManagement 管理依赖。
<?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"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring-cloud-demo</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.6</version> <relativePath/> </parent> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <java.version>17</java.version> <mybatis.version>3.0.3</mybatis.version> <mysql.version>8.0.33</mysql.version> <spring-cloud.version>2022.0.3</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter-test</artifactId> <version>${mybatis.version}</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> </project>
这里要注意,dependencyManagement 和 dependencies 是两种不同的依赖声明方式。前者声明依赖的版本和范围(不实际引入依赖),子模块可继承并省略版本号;后者直接引入依赖到当前模块。
子项目创建 - 商品服务
在父项目下创建子项目,完善 pom 文件引入 Web、MySQL、MyBatis 依赖。
<?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"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.example</groupId> <artifactId>spring-cloud-demo</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>product-service</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
完善启动类、配置文件及实体类、Controller、Service、Mapper。
server:
port: 9090
spring:
application:
name: product-service
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=false
username: root
password: MySQL 密码
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
@Data public class ProductInfo { private Integer id; private String productName; private Integer productPrice; private Integer state; private Date createTime; private Date updateTime; }
@RestController @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; @RequestMapping("/{productId}") public ProductInfo getProductById(@PathVariable("productId") Integer productId) { return productService.findProductInfo(productId); } }
@Slf4j @Service public class ProductService { @Autowired private ProductMapper productMapper; public ProductInfo findProductInfo(Integer id) { log.info("收到请求,id: " + id); return productMapper.selectProductById(id); } }
@Mapper public interface ProductMapper { @Select("select * from product_detail where id = #{id}") ProductInfo selectProductById(Integer id); }
运行访问 127.0.0.1:9090/product/1001 进行测试。
子项目创建 - 订单服务
pom.xml 结构与商品服务类似,注意修改 artifactId 为 order-service。
server:
port: 8080
spring:
application:
name: order-service
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: MySQL 密码
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
@Data public class OrderInfo { private Integer id; private Integer userId; private Integer productId; private Integer num; private Integer price; private Integer deleteFlag; private Date createTime; private Date updateTime; }
@RequestMapping("/order") @RestController public class OrderController { @Autowired private OrderService orderService; @RequestMapping("/{orderId}") public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId) { return orderService.findOrderInfoById(orderId); } }
@Service public class OrderService { @Autowired private OrderMapper orderMapper; public OrderInfo findOrderInfoById(Integer orderId) { OrderInfo orderInfo = orderMapper.selectOrderById(orderId); return orderInfo; } }
@Mapper public interface OrderMapper { @Select("select * from order_detail where id = #{orderId}") OrderInfo selectOrderById(Integer orderId); }
运行访问 127.0.0.1:8080/order/1 测试。
远程调用
在查询订单信息时,其中包括商品信息,因此,我们需要根据订单里的商品 ID 获取商品的详细信息。
那么,订单服务如何访问商品服务呢?之前我们在浏览器发送 HTTP 请求获取商品详细信息,同样的,也可以在 order-service 服务中向 product-service 服务发送一个 HTTP 请求,获取返回结果,并与订单信息整合,最后一起返回。
Spring 框架提供了 RestTemplate 来帮助我们发送 HTTP 请求,因此,我们定义 RestTemplate:
@Configuration public class BeanConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
对于接收的 ProductInfo,需要在 order-service 中创建商品信息类(与商品服务中的类结构一致)。
在订单信息 OrderInfo 中添加商品信息字段:
@Data public class OrderInfo { private Integer id; private Integer userId; private Integer productId; private Integer num; private Integer price; private Integer deleteFlag; private Date createTime; private Date updateTime; private ProductInfo productInfo; }
修改 OrderService,注入 RestTemplate 并进行远程调用:
@Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RestTemplate restTemplate; public OrderInfo findOrderInfoById(Integer orderId) { OrderInfo orderInfo = orderMapper.selectOrderById(orderId); String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId(); ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class); orderInfo.setProductInfo(productInfo); return orderInfo; } }
再次访问 127.0.0.1:8080/order/1 验证是否包含商品信息。
此时,虽然成功完成两个模块之间的远程通信,但项目中还存在着不少问题:
- 远程调用时,URL 的 IP 和端口号是写死的(127.0.0.1:9090),如果需要更换 IP,需要修改代码,是否可以实现让调用方不依赖服务提供方的 IP?
- 多机部署时,如何实现压力分摊?
- 商品信息代码重复编写,是否可以实现不同模块重复代码共用?
在之后的文章中,我们就来进一步学习如何使用 Spring Cloud 中的组件来解决这些问题。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online