微服务架构简介:Spring Cloud 2023.x 全栈入门与企业级实践
想象一下,你正在开发一个大型电商平台。起初,商品、订单、用户、支付所有功能都揉在一个“大泥球”应用里。一次“双十一”大促,订单模块一个BUG导致CPU飙升至100%,整个平台随之宕机,损失惨重。事后复盘,团队痛定思痛,决定将系统拆分为多个独立部署、职责单一的小型服务——这就是微服务转型的典型起点。
1. 一个真实的“微服务时刻”
场景:“极客商城”初期采用Spring Boot开发单体应用。随着业务激增,系统暴露出诸多问题:
- 部署臃肿:哪怕只改一行商品描述,也需要打包整个1GB的应用,部署耗时30分钟。
- 技术栈僵化:整个系统被Java 8和Spring 5.2“锁死”,难以引入新的技术框架。
- 扩展困难:订单模块是CPU密集型,用户模块是I/O密集型,但只能整体水平扩展,资源浪费严重。
- 牵一发而动全身:支付模块的一个依赖升级,可能导致整个应用启动失败。
转型决策:技术团队决定采用微服务架构。经过选型,他们锁定了Spring Cloud 2023.x,因为它能与熟悉的Spring Boot 3.x完美集成,并提供了服务发现、配置管理、负载均衡、熔断限流等“开箱即用”的分布式系统解决方案,极大地降低了开发复杂度。
2. 什么是微服务架构?
微服务架构是一种将大型复杂软件应用拆分为一组小型服务的架构风格。每个服务都围绕单一业务能力构建,可以独立开发、部署、扩展和故障隔离,并通过轻量级通信机制(通常是HTTP/REST)协同工作。
与单体架构的直观对比:
| 特性 | 单体架构 (Monolithic) | 微服务架构 (Microservices) |
|---|---|---|
| 代码结构 | 一个代码仓库,所有功能模块在一起 | 多个独立的代码仓库/项目 |
| 数据库 | 通常共享一个数据库 | 每个服务拥有自己的数据库(数据库隔离) |
| 技术栈 | 相对统一,技术升级风险大 | 可按服务选择最合适的技术(多语言) |
| 部署 | 整体部署,耦合度高 | 独立部署,服务间解耦 |
| 扩展 | 只能整体水平扩展,不精细 | 可按需对特定服务进行细粒度扩展 |
| 容错 | 一个模块故障可能导致整个系统宕机 | 故障被隔离在单个服务内 |
| 团队协作 | 适合小团队,沟通成本低 | 适合大型团队,可按照服务边界划分“双披萨团队” |
思考:微服务并非银弹。它引入了服务治理、分布式事务、链路追踪、运维监控等新的复杂性。选择Spring Cloud,正是为了系统性地解决这些分布式难题。
3. 为什么是Spring Cloud 2023.x + Spring Boot 3.2.x + JDK 17?
这是一套为未来而生的技术组合:
- Spring Boot 3.2.x: 基于Spring Framework 6.x,支持GraalVM原生镜像,启动更快,内存占用更低。它要求最低JDK 17。
- JDK 17 (LTS): Oracle发布的长期支持版本,提供了密封类、模式匹配、文本块等现代语言特性,是未来数年的生产环境标准。
- Spring Cloud 2023.x (代号“ Leyton”): 这是与Spring Boot 3.x完全兼容的首个版本系列。它移除了Netflix Ribbon、Hystrix等已进入维护模式的组件,转而拥抱Spring Cloud LoadBalancer和Resilience4j,并深度集成Spring Cloud Alibaba,是技术栈的“重置”与升级。
版本兼容性(务必牢记!):
<!-- 核心版本对应关系 -->
Spring Boot 3.2.x ←→ Spring Cloud 2023.x (必须!)
Spring Boot 3.1.x ←→ Spring Cloud 2022.x
Spring Boot 2.7.x ←→ Spring Cloud 2021.x
常见错误1: 在Spring Boot 3.x项目中使用Spring Cloud 2021.x,会导致大量ClassNotFoundException。务必使用匹配的版本。
4. Spring Cloud微服务核心组件与架构图
一个完整的Spring Cloud微服务生态通常包含以下核心组件,它们共同协作,管理着分布式系统的复杂性:
架构图:Spring Cloud微服务生态系统
┌─────────────────────────────────────────────────────────────────────────┐
│ External Clients (Web/Mobile/API) │
└───────────────────────────────┬─────────────────────────────────────────┘
│ (HTTP/HTTPS)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ API Gateway (Spring Cloud Gateway) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────────────┐ │
│ │ Routing │ │ Filter │ │ LoadBalancerClientFilter │ │
│ │ (Predicates)│ │(Auth/Limit) │ │ (服务发现与负载均衡) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
│ (Service Discovery & Load Balancing) │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Service A │ │ Service B │
│ (user-service) │ │ (order-service) │
│ │ │ │
│ ┌──────────┐ │ (Feign/RestTemplate) │ ┌──────────┐ │
│ │ Controller │◀───────────────────────────│ │ Controller │ │
│ └──────────┘ │ │ └──────────┘ │
│ │ │ │ │ │
│ ┌──────────┐ │ │ ┌──────────┐ │
│ │ Service │ │ │ │ Service │ │
│ └──────────┘ │ │ └──────────┘ │
│ │ │ │ │ │
│ ┌──────────┐ │ │ ┌──────────┐ │
│ │ Mapper │ │ │ │ Mapper │ │
│ └──────────┘ │ │ └──────────┘ │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ ┌──────────┐ │ │ ┌──────────┐ │
│ │ MySQL │ │ │ │ MySQL │ │
│ │ Redis │ │ │ │ Redis │ │
│ └──────────┘ │ │ └──────────┘ │
└─────────────────┘ └─────────────────┘
▲ ▲
└──────────────────────┬───────────────────────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌────────────────────┐
│ Service Registry│ │ Config Center │ │ Circuit Breaker & │
│ & Discovery │ │ (Nacos) │ │ Rate Limiter │
│ (Nacos) │ │ │ │ (Sentinel) │
└─────────────────┘ └──────────────────┘ └────────────────────┘
│ │ │
┌─────────────────────────────────────────────────────────────────────────┐
│ Observability (监控、日志、链路追踪) │
│ Sleuth/Micrometer + Prometheus + Grafana │
└─────────────────────────────────────────────────────────────────────────┘
组件职责:
- Nacos: 兼具服务注册与发现和动态配置管理两大核心功能,是微服务的信息中枢。
- Spring Cloud Gateway: API网关,所有外部请求的统一入口,负责路由、过滤、限流、鉴权。
- Spring Cloud LoadBalancer: 客户端负载均衡器,替代了Netflix Ribbon,从注册中心获取服务列表并进行负载均衡调用。
- OpenFeign: 声明式的HTTP客户端,让远程服务调用像调用本地方法一样简单。
- Sentinel: 流量控制、熔断降级、系统自适应保护,保障服务的高可用性。
- Sleuth/Micrometer: 分布式链路追踪和指标收集,是排查复杂调用链问题的“眼睛”。
5. 企业级微服务项目搭建
让我们从零开始,搭建一个包含用户服务和订单服务的微服务Demo。订单服务需要通过Feign调用用户服务。
5.1 项目结构
microservices-demo/
├── pom.xml (父工程,统一依赖管理)
├── user-service/ (用户服务,端口8081)
├── order-service/ (订单服务,端口8082)
└── api-gateway/ (网关服务,端口8080)
5.2 父工程 (microservices-demo/pom.xml)
父POM的核心作用是统一管理所有子模块的依赖版本,这是企业级项目规范化的第一步。
<?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>com.geekbang</groupId>
<artifactId>microservices-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <!-- 注意:打包方式为pom -->
<name>microservices-demo</name>
<description>Spring Cloud 2023.x 微服务入门示例</description>
<modules>
<module>user-service</module>
<module>order-service</module>
<module>api-gateway</module>
</modules>
<!-- 版本属性定义:这是核心! -->
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!-- Spring生态核心版本 -->
<spring-boot.version>3.2.5</spring-boot.version>
<spring-cloud.version>2023.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.1.2</spring-cloud-alibaba.version>
<!-- 其他组件版本 -->
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<mysql-connector.version>8.3.0</mysql-connector.version>
<springdoc.version>2.5.0</springdoc.version>
<lombok.version>1.18.30</lombok.version>
</properties>
<!-- 依赖管理:锁定所有子模块的依赖版本 -->
<dependencyManagement>
<dependencies>
<!-- Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba BOM -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!-- SpringDoc OpenAPI (Swagger 3) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- Spring Boot Maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- 指定Maven编译版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
5.3 用户服务 (user-service/pom.xml)
用户服务提供基础的CRUD和查询接口。
<?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>microservices-demo</artifactId>
<groupId>com.geekbang</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-service</artifactId>
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Actuator (健康检查,服务注册依赖) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- SpringDoc API文档 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
5.4 用户服务配置 (user-service/src/main/resources/application.yml)
这是微服务的“生命线”,配置的准确性至关重要。
server:
port: 8081
spring:
application:
name: user-service # 服务名,注册中心以此识别
profiles:
active: dev
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/geek_user?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root123
# Redis配置
data:
redis:
host: localhost
port: 6379
database: 0
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
# Nacos 服务发现配置
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos Server地址
namespace: public # 命名空间,默认为public
group: DEFAULT_GROUP # 分组,默认为DEFAULT_GROUP
# 服务实例配置
ip: 127.0.0.1
port: ${server.port}
heartbeat-interval: 5000ms # 心跳间隔
# 重要:如果本机IP不是127.0.0.1,可能需要显式指定
# ip: ${spring.cloud.client.ip-address}
# MyBatis-Plus配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 自动转换下划线到驼峰命名
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台输出SQL日志(开发环境用)
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
# SpringDoc OpenAPI配置
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
enabled: true
# 暴露Actuator端点,Nacos健康检查依赖于此
management:
endpoints:
web:
exposure:
include: "*" # 生产环境应精细化控制,如"health,info"
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
5.5 用户服务主启动类
package com.geekbang.userservice;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 用户服务启动类
* @EnableDiscoveryClient 注解用于开启服务注册与发现功能
*/
@SpringBootApplication
@EnableDiscoveryClient // 关键注解:声明本服务为Nacos客户端
@MapperScan("com.geekbang.userservice.mapper") // 扫描MyBatis Mapper接口
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
System.out.println(">>>>>> 用户服务(User-Service)启动成功,端口:8081 <<<<<<");
}
}
5.6 用户服务核心代码
1. 统一响应实体 (Result)
package com.geekbang.userservice.common;
import lombok.Data;
import java.io.Serializable;
/**
* 统一API响应结果封装
* @param <T> 数据泛型
*/
@Data
public class Result<T> implements Serializable {
private Integer code; // 状态码,200成功,500失败
private String message; // 提示信息
private T data; // 响应数据
private Long timestamp; // 时间戳
public Result() {
this.timestamp = System.currentTimeMillis();
}
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
// 成功静态方法
public static <T> Result<T> success() {
return new Result<>(200, "操作成功", null);
}
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static <T> Result<T> success(String message, T data) {
return new Result<>(200, message, data);
}
// 失败静态方法
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
}
2. 用户实体、Mapper、Service、Controller (简化版)
// User.java
package com.geekbang.userservice.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String email;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
// UserController.java
package com.geekbang.userservice.controller;
import com.geekbang.userservice.common.Result;
import com.geekbang.userservice.entity.User;
import com.geekbang.userservice.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor // Lombok构造器注入
@Slf4j
@Tag(name = "用户管理", description = "用户相关接口")
public class UserController {
private final UserService userService;
@Operation(summary = "根据ID查询用户")
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable Long id) {
log.info("查询用户,ID: {}", id);
User user = userService.getById(id);
if (user == null) {
return Result.error("用户不存在");
}
return Result.success(user);
}
@Operation(summary = "分页查询用户")
@GetMapping("/page")
public Result<?> page(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
log.info("分页查询用户,pageNum: {}, pageSize: {}", pageNum, pageSize);
// 此处省略具体分页逻辑,返回模拟数据
return Result.success("获取第" + pageNum + "页数据,每页" + pageSize + "条");
}
}
5.7 订单服务 (order-service)
订单服务需要远程调用用户服务,因此需要引入Feign。
order-service/pom.xml 额外依赖
<!-- OpenFeign 声明式HTTP客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- LoadBalancer 负载均衡 (Spring Cloud 2023.x 已内置,无需额外引入) -->
<!-- Sentinel 熔断降级 (可选) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
订单服务配置 (application.yml)
server:
port: 8082
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
# Sentinel配置
sentinel:
transport:
dashboard: localhost:8858 # Sentinel控制台地址
eager: true # 饥饿加载,防止首次调用失败
# Feign客户端配置
feign:
client:
config:
default: # 全局配置,也可针对特定服务(user-service)配置
connectTimeout: 5000 # 连接超时时间(ms)
readTimeout: 5000 # 读取超时时间(ms)
loggerLevel: basic # 日志级别: NONE, BASIC, HEADERS, FULL
sentinel:
enabled: true # 开启Feign对Sentinel的支持
# 其他配置同user-service...
订单服务主启动类
package com.geekbang.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 订单服务启动类
* @EnableFeignClients 开启Feign客户端功能
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 关键注解:开启Feign
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
System.out.println(">>>>>> 订单服务(Order-Service)启动成功,端口:8082 <<<<<<");
}
}
Feign客户端接口 (UserFeignClient)
package com.geekbang.orderservice.feign;
import com.geekbang.orderservice.common.Result;
import com.geekbang.orderservice.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* 声明式HTTP客户端
* name/value: 指定要调用的服务名(在Nacos中注册的名字)
* fallbackFactory: 指定服务降级处理工厂类
*/
@FeignClient(name = "user-service", fallbackFactory = UserFallbackFactory.class)
public interface UserFeignClient {
/**
* 调用用户服务的 /user/{id} 接口
* 方法签名(URL、请求方法、参数、返回值)需要与被调用方保持一致
*/
@GetMapping("/user/{id}")
Result<User> getUserById(@PathVariable("id") Long id);
}
Feign降级处理工厂 (UserFallbackFactory)
package com.geekbang.orderservice.feign;
import com.geekbang.orderservice.common.Result;
import com.geekbang.orderservice.entity.User;
import feign.hystrix.FallbackFactory; // 注意:Spring Cloud 2023.x 已移除Hystrix
// 正确导入:使用Sentinel的FallbackFactory
import com.alibaba.cloud.sentinel.fallback.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* UserFeignClient的降级处理工厂
* 当调用user-service失败(超时、异常、熔断)时,会执行此处逻辑
*/
@Component
@Slf4j
public class UserFallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable cause) {
return new UserFeignClient() {
@Override
public Result<User> getUserById(Long id) {
log.error("调用用户服务失败,用户ID: {}, 异常原因: {}", id, cause.getMessage(), cause);
// 返回一个友好的降级响应
User fallbackUser = new User();
fallbackUser.setId(id);
fallbackUser.setUsername("【用户服务暂不可用】");
fallbackUser.setEmail("N/A");
return Result.error("用户服务调用失败,已降级处理");
// 也可以返回一个带兜底数据的成功结果
// return Result.success(fallbackUser);
}
};
}
}
订单控制器 (OrderController)
package com.geekbang.orderservice.controller;
import com.geekbang.orderservice.common.Result;
import com.geekbang.orderservice.entity.Order;
import com.geekbang.orderservice.feign.UserFeignClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/order")
@RequiredArgsConstructor
@Slf4j
public class OrderController {
private final UserFeignClient userFeignClient; // 注入Feign客户端
@GetMapping("/{orderId}")
public Result<?> getOrderDetail(@PathVariable Long orderId,
@RequestParam(required = false) Long userId) {
log.info("查询订单详情,订单ID: {}", orderId);
Order order = new Order(); // 模拟订单数据
order.setId(orderId);
order.setOrderNo("ORD" + System.currentTimeMillis());
// 关键:通过Feign远程调用用户服务,获取用户信息
if (userId != null) {
Result<?> userResult = userFeignClient.getUserById(userId);
order.setUserInfo("关联用户信息: " + userResult);
} else {
order.setUserInfo("未关联用户");
}
return Result.success(order);
}
}
5.8 API网关服务 (api-gateway)
网关是流量的统一入口。
api-gateway/pom.xml 主要依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 注意:Gateway不能引入spring-boot-starter-web,它基于WebFlux非阻塞模型 -->
网关配置 (application.yml)
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
# Gateway 路由配置
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
lower-case-service-id: true # 服务名小写
routes: # 静态路由配置(优先级高于动态发现)
- id: user-service-route
uri: lb://user-service # lb:// 表示负载均衡到user-service
predicates:
- Path=/api/user/** # 匹配路径
filters:
- StripPrefix=1 # 去掉前缀/api,再转发给user-service
- AddRequestHeader=X-Request-Gateway, api-gateway # 添加请求头
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1
# 其他配置...
6. 核心组件详解
- Nacos: 启动后,服务向Nacos注册自己的
IP:Port,并定时发送心跳。消费者从Nacos拉取服务列表,实现服务发现。配置中心功能允许运行时动态修改配置并推送给服务。 - OpenFeign: 基于动态代理,在启动时解析
@FeignClient接口,生成实现类。发起调用时,集成LoadBalancer从服务列表中选择一个实例,构建HTTP请求。集成了Sentinel或Resilience4j实现熔断。 - Spring Cloud Gateway: 基于WebFlux响应式编程模型,高性能。核心概念:Route(路由)、Predicate(断言)、Filter(过滤器)。
lb://前缀集成了负载均衡。
7. Spring Cloud服务发现与负载均衡内部机制
流程图:服务调用时序图
┌─────────┐ 1.启动注册 ┌─────────┐ 2.订阅服务 ┌─────────┐
│User Service│───────────▶│ Nacos │◀─────────────│Order Service│
│ (Provider) │ │ Server │ │(Consumer) │
└─────┬─────┘ └─────┬────┘ └─────┬─────┘
│ │ │
│ 3.定时心跳(5s) │ 4.推送服务列表变更 │
│───────────────────────▶│───────────────────────▶│
│ │ │
│ │ │
│ │ 5.调用 /user/1 │
│ │◀────────────────────────│
│ 6.负载均衡选择实例 │ │
│───────────────────────▶│ │
│ 7.HTTP请求 (IP1:8081) │ │
│◀───────────────────────│ │
│ 8.返回响应 │ │
│───────────────────────▶│───────────────────────▶│
│ │ │
内部机制:
- 自动注册:
@EnableDiscoveryClient注解使服务在启动后,自动向Nacos Server发送注册请求(包含服务名、IP、端口、元数据)。 - 服务订阅:
UserFeignClient在创建动态代理时,会向Nacos订阅user-service的服务列表,并缓存在本地。 - 负载均衡:
BlockingLoadBalancerClient(Spring Cloud LoadBalancer) 从本地缓存的服务列表中,通过默认的RoundRobinLoadBalancer(轮询)策略选择一个实例。 - 调用与容错: Feign构造HTTP请求发送到选中的实例。如果开启Sentinel,会先经过熔断器和流控规则检查。
8. application.yml全解析与最佳实践
spring.application.name: 必须全局唯一,是服务间识别的标识。命名规范:业务-功能-service,如pay-notify-service。spring.cloud.nacos.discovery:namespace: 用于多环境隔离(如dev, test, prod)。group: 用于大业务模块内分组。cluster-name: 用于同地域多机房容灾。
feign.client.config: 超时配置是关键。connectTimeout建立TCP连接超时,readTimeout等待响应超时。生产环境应根据服务SLA(服务等级协议)调整,通常设置connectTimeout=2000ms, readTimeout=5000ms。management.endpoints.web.exposure.include: 生产环境应严格管理。不要直接设置为*,建议只暴露health, info, metrics, prometheus等必要端点。- 多环境配置: 使用
spring.profiles.active=dev/prod和application-{profile}.yml文件来管理不同环境的配置(数据库地址、Redis地址、日志级别等)。
9. 四大常见错误与解决方案
错误1:版本不兼容导致启动失败
- 错误现象:
NoSuchMethodError,ClassNotFoundException,或启动时直接报错Spring Boot version not supported。 - 错误配置:
<spring-boot.version>3.2.5</spring-boot.version> <spring-cloud.version>2021.0.8</spring-cloud.version> <!-- 错误!版本不匹配 --> - 解决方案: 严格遵循https://spring.io/projects/spring-cloud的对应关系。使用Spring Initializr (start.spring.io)生成项目可避免此问题。
错误2:服务注册失败,在Nacos控制台看不到服务
- 错误日志:
Failed to register service ... NacosRegistration failed... - 可能原因1: Nacos Server未启动或网络不通。检查
8848端口。 - 可能原因2: Actuator端点未暴露,Nacos客户端依赖
/actuator/health端点进行健康检查。 - 正确配置:
management: endpoints: web: exposure: include: health,info # 至少暴露health endpoint: health: show-details: always
错误3:Feign调用报超时Read timed out,但配置似乎不生效
- 错误配置:
# 错误:在spring.cloud下配置 spring: cloud: feign: client: config: default: connectTimeout: 5000 - 正确配置: Feign的配置根节点是
feign,不是spring.cloud。feign: client: config: default: connectTimeout: 5000 readTimeout: 8000
错误4:Gateway路由配置了但不生效,返回404
- 错误配置:
spring: cloud: gateway: routes: - id: test-route uri: http://localhost:8081 # 错误:未使用lb:// predicates: - Path=/user/** filters: - StripPrefix=0 # 顺序或参数错误 - 解决方案:
- 确保使用了
lb://service-name格式进行服务发现和负载均衡。 - 检查
predicates的匹配规则是否正确。访问/api/user/1,配置Path=/api/user/**,配合StripPrefix=1,转发到user-service的路径是/user/1。 - 开启调试日志:
logging.level.org.springframework.cloud.gateway=DEBUG,查看路由匹配过程。
- 确保使用了
10. 性能与安全
- 性能优化:
- 连接池: Feign底层默认使用JDK的
HttpURLConnection,性能差。推荐使用OkHttp或Apache HttpClient。<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency> - 缓存: 对热点数据(如用户信息、配置信息)使用Redis缓存,减少数据库和远程调用压力。
- 异步与响应式: 对I/O密集型操作,考虑使用
CompletableFuture或响应式编程WebFlux,提高并发能力。
- 连接池: Feign底层默认使用JDK的
- 安全防护:
- 网关层认证: 在
Gateway的GlobalFilter中集成JWT或OAuth2.0,进行统一身份认证和鉴权。 - 接口防刷: 在网关或Sentinel中配置接口的QPS限流,防止恶意攻击。
- 配置安全: 数据库密码、Redis密码、第三方密钥等敏感信息,不应明文写在
application.yml中。应使用配置中心的加密功能或阿里云KMS等密钥管理服务。
- 网关层认证: 在
11. 全链路启动、注册、调用演示
步骤1:启动基础设施
- 启动Nacos Server (单机模式):
sh startup.sh -m standalone - 启动Redis
- 启动MySQL,创建
geek_user数据库和t_user表。
步骤2:启动微服务
按顺序启动:user-service -> order-service -> api-gateway
控制台日志输出示例:
# User Service 启动日志
... o.s.c.a.n.registry.NacosAutoServiceRegistration : Auto service registration finished
... UserServiceApplication : >>>>>> 用户服务(User-Service)启动成功,端口:8081 <<<<<<
# 注册成功日志
... com.alibaba.nacos.client.naming : [REGISTER-SERVICE] public registering service user-service ...
# Order Service 启动日志
... o.s.c.o.f.FeignClientFactoryBean : Created Feign client 'user-service'
... OrderServiceApplication : >>>>>> 订单服务(Order-Service)启动成功,端口:8082 <<<<<<
步骤3:验证Nacos服务注册
打开浏览器访问 http://localhost:8848/nacos (默认账号/密码: nacos/nacos)。
在服务管理 -> 服务列表中,应看到user-service和order-service两个服务,状态为健康。
步骤4:通过网关调用接口验证
-
直接调用用户服务:
curl -X GET "http://localhost:8081/user/1"预期输出:
{ "code": 200, "message": "操作成功", "data": { "id": 1, "username": "testUser", "email": "test@example.com", "createTime": "2024-01-01T10:00:00", "updateTime": "2024-01-01T10:00:00" }, "timestamp": 1712668888888 } -
通过网关调用用户服务:
curl -X GET "http://localhost:8080/api/user/1"预期输出:与上面一致。证明网关路由
/api/user/**->lb://user-service生效。 -
订单服务通过Feign调用用户服务:
curl -X GET "http://localhost:8082/order/100?userId=1"预期输出:
{ "code": 200, "message": "操作成功", "data": { "id": 100, "orderNo": "ORD1712668889999", "userInfo": "关联用户信息: {\"code\":200,\"message\":\"操作成功\",\"data\":{\"id\":1,\"username\":\"testUser\", ...}}" }, "timestamp": 1712668890000 }关键观察:
userInfo字段包含了从用户服务获取的数据,证明服务间远程调用成功。 -
模拟用户服务宕机,测试熔断降级:
停止user-service,再次调用curl -X GET "http://localhost:8082/order/100?userId=1"。
预期输出(降级后):{ "code": 500, "message": "用户服务调用失败,已降级处理", "data": { "id": 100, "orderNo": "ORD1712668891111", "userInfo": "关联用户信息: {\"code\":500,\"message\":\"用户服务调用失败,已降级处理\",\"data\":null}" }, "timestamp": 1712668892000 }同时,订单服务控制台会打印错误日志:
ERROR c.g.o.f.UserFallbackFactory - 调用用户服务失败,用户ID: 1, 异常原因: ... connect timed out ...证明Feign整合Sentinel的熔断降级机制生效,系统具备了一定的容错能力。
12. 总结与进阶
微服务架构的优缺点总结:
- 优点:
- 技术异构: 不同服务可用不同技术栈。
- 弹性扩展: 按需伸缩,资源利用率高。
- 高容错: 故障被隔离,不会蔓延。
- 独立交付: 小团队可独立开发、测试、部署。
- 缺点/挑战:
- 复杂度转移: 从单体应用内部复杂度,转变为分布式系统复杂度(网络、事务、一致性、调试、监控)。
- 运维成本高: 需要成熟的DevOps、容器化、监控体系支撑。
- 分布式事务: 数据一致性保证困难,需引入Saga、Seata等方案。
- 测试复杂性: 需要完善的集成测试、契约测试、端到端测试。
后续学习路径:
- 服务治理深入: 链路追踪(Sleuth/Zipkin, SkyWalking)、分布式配置中心(Nacos Config)、消息总线(Spring Cloud Bus)。
- 流量治理: Sentinel规则持久化、热点参数限流、系统自适应保护、网关聚合限流。
- 安全架构: 集成Spring Security OAuth2.0,实现统一的认证授权中心。
- 容器化与编排: 将服务打包为Docker镜像,使用Kubernetes进行编排、服务发现、自动伸缩。
- 服务网格(Service Mesh): 了解Istio,将流量管理、可观测性、安全等能力下沉到基础设施层。
微服务化是一场架构演进,而非单纯的技术叠加。Spring Cloud 2023.x为我们提供了强大、规范的工具集,但真正的挑战在于如何根据业务边界合理地拆分服务,并建立与之匹配的团队协作、工程效能和运维体系。
在你的项目或你了解的业务中,哪个模块最适合最先被拆分为独立的微服务?为什么?欢迎在评论区留言讨论。
更多推荐




所有评论(0)