Spring Cloud 概述

什么是 Spring Cloud

Spring Cloud 是基于 Spring Boot 的微服务架构开发工具集为开发者提供了在分布式系统如配置管理服务发现断路器智能路由微代理控制总线一次性令牌全局锁领导选举分布式会话集群状态中快速构建一些常见模式的工具

  • 核心功能
    • 服务注册与发现自动管理服务实例
    • 配置中心集中管理配置文件
    • 负载均衡客户端负载均衡
    • 熔断器服务容错和降级
    • API 网关统一入口和路由
    • 消息驱动事件驱动架构
  • 主要优势
    • 开箱即用基于 Spring Boot快速启动
    • 生态完善涵盖微服务开发的各个方面
    • 社区活跃大量生产实践案例
    • 灵活扩展可替换各个组件的实现

Spring Cloud 的核心概念

微服务架构

将单体应用拆分为多个小型独立部署的服务每个服务专注于单一职责

1
2
3
传统单体架构 vs 微服务架构:
单体: 所有功能在一个应用中耦合度高扩展困难
微服务: 功能拆分为多个服务独立部署易于扩展和维护

服务治理

管理和协调微服务之间的通信和依赖关系

组件 功能 代表实现
服务注册与发现 管理服务实例的注册和发现 Eureka, Nacos, Consul
配置中心 集中管理配置信息 Config Server, Nacos Config
负载均衡 分散请求到多个服务实例 Ribbon, LoadBalancer
熔断器 防止雪崩效应服务降级 Hystrix, Sentinel, Resilience4j
API 网关 统一入口路由转发 Zuul, Gateway
消息总线 配置刷新通知 Bus, RocketMQ

Spring Cloud 的工作原理

服务调用流程

1
2
3
4
5
6
1. 服务提供者启动时向注册中心注册
2. 服务消费者从注册中心获取服务列表
3. 通过负载均衡选择具体实例
4. 发起远程调用REST/RPC
5. 熔断器监控调用状态
6. 异常时触发降级逻辑

配置刷新流程

1
2
3
4
5
1. 修改配置中心的配置
2. 发送刷新通知到消息总线
3. 各服务实例监听消息总线
4. 接收到通知后重新加载配置
5. 无需重启服务即可生效

环境搭建

版本对应关系

Spring Cloud 与 Spring Boot 有严格的版本对应关系

Spring Cloud Spring Boot
2022.0.x (Kilburn) 3.0.x
2021.0.x (Jubilee) 2.6.x
2020.0.x (Ilford) 2.4.x
Hoxton 2.2.x, 2.3.x
Greenwich 2.1.x

最新稳定版推荐使用 Spring Cloud 2022.0.x + Spring Boot 3.0.x

父工程配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?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.example</groupId>
<artifactId>cloud-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<properties>
<java.version>17</java.version>
<spring-boot.version>3.0.0</spring-boot.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<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 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

服务注册与发现

Eureka

添加依赖

1
2
3
4
5
6
7
8
9
10
11
<!-- Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Eureka Server

启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

配置文件

1
2
3
4
5
6
7
8
9
10
11
server:
port: 8761

eureka:
instance:
hostname: localhost
client:
register-with-eureka: false # 不注册自己
fetch-registry: false # 不拉取注册表
server:
enable-self-preservation: false # 关闭自我保护开发环境

Eureka Client

启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8081

spring:
application:
name: user-service

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true # 使用 IP 注册

Nacos推荐

添加依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2022.0.0.0</version>
</dependency>

启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8081

spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: dev # 命名空间
group: DEFAULT_GROUP

安装 Nacos

1
2
3
4
5
6
7
8
9
10
11
12
13
# 下载 Nacos
wget https://github.com/alibaba/nacos/releases/download/2.2.0/nacos-server-2.2.0.tar.gz

# 解压
tar -xzf nacos-server-2.2.0.tar.gz

# 启动单机模式
cd nacos/bin
sh startup.sh -m standalone

# 访问控制台
# http://localhost:8848/nacos
# 默认用户名/密码nacos/nacos

服务调用

RestTemplate + LoadBalancer

配置 RestTemplate

1
2
3
4
5
6
7
8
9
@Configuration
public class RestTemplateConfig {

@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

服务调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class OrderService {

@Autowired
private RestTemplate restTemplate;

public Order createOrder(Order order) {
// 通过服务名调用
String url = "http://user-service/users/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);

// 业务逻辑
order.setUserName(user.getName());
return orderRepository.save(order);
}
}

OpenFeign推荐

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启用 Feign

1
2
3
4
5
6
7
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}

定义 Feign 接口

1
2
3
4
5
6
7
8
9
@FeignClient(name = "user-service")
public interface UserClient {

@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Integer id);

@PostMapping("/users")
User createUser(@RequestBody User user);
}

使用 Feign 客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class OrderService {

@Autowired
private UserClient userClient;

public Order createOrder(Order order) {
// 直接调用接口像调用本地方法一样
User user = userClient.getUserById(order.getUserId());

order.setUserName(user.getName());
return orderRepository.save(order);
}
}

Feign 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
feign:
client:
config:
user-service: # 服务名
connectTimeout: 5000
readTimeout: 5000
loggerLevel: FULL # 日志级别
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true

配置中心

Spring Cloud Config

Config Server

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8888

spring:
cloud:
config:
server:
git:
uri: https://github.com/example/config-repo.git
search-paths: configs
username: your-username
password: your-password

Config Client

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

配置文件bootstrap.yml

1
2
3
4
5
6
7
8
spring:
application:
name: user-service
cloud:
config:
uri: http://localhost:8888
profile: dev
label: main

动态刷新配置

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RefreshScope // 支持动态刷新
public class ConfigController {

@Value("${app.message:Hello}")
private String message;

@GetMapping("/message")
public String getMessage() {
return message;
}
}

Nacos Config推荐

添加依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2022.0.0.0</version>
</dependency>

配置文件bootstrap.yml

1
2
3
4
5
6
7
8
9
10
spring:
application:
name: user-service
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
namespace: dev
group: DEFAULT_GROUP

动态刷新

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RefreshScope
public class ConfigController {

@Value("${app.message:Hello}")
private String message;

@GetMapping("/message")
public String getMessage() {
return message;
}
}

负载均衡

Spring Cloud LoadBalancer

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

负载均衡策略

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class LoadBalancerConfig {

@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}

内置策略

策略 说明
RoundRobinLoadBalancer 轮询默认
RandomLoadBalancer 随机
WeightedResponseTimeLoadBalancer 加权响应时间

熔断器

Sentinel推荐

添加依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2022.0.0.0</version>
</dependency>

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel 控制台地址
datasource:
ds1:
nacos:
server-addr: localhost:8848
data-id: ${spring.application.name}-sentinel-rules
group-id: DEFAULT_GROUP
rule-type: flow

流控规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class UserService {

@SentinelResource(value = "getUserById", blockHandler = "handleBlock")
public User getUserById(Integer id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
}

// 降级处理方法
public User handleBlock(Integer id, BlockException ex) {
log.warn("触发限流或降级: {}", id);
return new User(); // 返回默认值
}
}

安装 Sentinel

1
2
3
4
5
6
7
8
9
# 下载 Sentinel
wget https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-1.8.6.jar

# 启动
java -jar sentinel-dashboard-1.8.6.jar

# 访问控制台
# http://localhost:8080
# 默认用户名/密码sentinel/sentinel

Resilience4j

添加依赖

1
2
3
4
5
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.1</version>
</dependency>

熔断器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class UserService {

@CircuitBreaker(name = "userService", fallbackMethod = "fallback")
public User getUserById(Integer id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
}

public User fallback(Integer id, Throwable t) {
log.error("服务调用失败: {}", t.getMessage());
return new User();
}
}
1
2
3
4
5
6
7
8
resilience4j:
circuitbreaker:
instances:
userService:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
permitted-number-of-calls-in-half-open-state: 5

API 网关

Spring Cloud Gateway

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 8080

spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1

- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1

自定义过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class AuthFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = request.getHeaders().getFirst("Authorization");

if (token == null || !token.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}

// 验证 Token
// ...

return chain.filter(exchange);
}

@Override
public int getOrder() {
return -1; // 优先级
}
}

限流配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@ipKeyResolver}"
1
2
3
4
5
6
7
8
9
10
@Configuration
public class RateLimiterConfig {

@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
}

最佳实践

服务拆分原则

1
2
3
4
5
1. 单一职责每个服务只负责一个业务领域
2. 独立部署服务可以独立部署和升级
3. 数据隔离每个服务有自己的数据库
4. 轻量通信使用 REST 或 gRPC 进行通信
5. 容错设计考虑服务失败的情况

配置管理

1
2
3
4
5
6
7
8
9
# 使用环境变量敏感信息
spring:
datasource:
password: ${DB_PASSWORD}

# 不同环境使用不同配置文件
spring:
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}

日志追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用 MDC 记录链路 ID
@Component
public class TraceIdFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String traceId = UUID.randomUUID().toString();
MDC.put("traceId", traceId);

ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("X-Trace-Id", traceId);

return chain.filter(exchange).doFinally(signalType -> MDC.clear());
}

@Override
public int getOrder() {
return -1;
}
}

监控告警

1
2
3
4
5
<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1
2
3
4
5
6
7
8
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always

常见问题

服务注册失败

1
2
3
4
5
6
7
8
9
10
11
问题服务无法注册到注册中心

错误原因
1. 注册中心未启动
2. 网络不通
3. 配置错误

解决方案
1. 检查注册中心是否正常运行
2. 确认网络连接正常
3. 检查 server-addr 配置是否正确

服务调用超时

1
2
3
4
5
6
7
8
9
问题Feign 调用超时

解决方案
1. 增加超时时间
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000

2. 优化服务性能
3. 使用异步调用

报错处理

💗💗 Spring Cloud 报错No instances available

1
2
3
4
5
6
7
8
9
10
11
12
错误信息
No instances available for xxx-service

错误原因
1. 服务未注册
2. 服务名不匹配
3. 注册中心连接失败

解决方案
1. 检查服务是否正常启动并注册
2. 确认服务名与注册中心一致
3. 检查注册中心配置

💗💗 Spring Cloud 报错ConnectTimeoutException

1
2
3
4
5
6
7
8
9
10
11
12
错误信息
ConnectTimeoutException: Connect to xxx timed out

错误原因
1. 目标服务未启动
2. 网络问题
3. 超时时间设置过短

解决方案
1. 确认目标服务正常运行
2. 检查网络连接
3. 增加超时时间配置

学习资源

  • 视频
    • 尚硅谷 SpringCloud 教程https://www.bilibili.com/video/BV1UJc2ezEFU
  • 官方文档
    • Spring Cloud 官方文档https://spring.io/projects/spring-cloud
    • Spring Cloud Alibabahttps://spring-cloud-alibaba-group.github.io/github-pages/hoxton/zh-cn/index.html
  • 书籍
    • Spring Cloud 微服务实战翟永超著
    • 深入理解 Spring Cloud 与微服务构建方志朋著
  • 教程
    • Spring Cloud 入门教程https://www.baeldung.com/category/spring-cloud/
    • Baeldung Spring Cloud 教程https://www.baeldung.com/category/spring-cloud/
  • 社区
    • Stack Overflow Spring Cloud 标签https://stackoverflow.com/questions/tagged/spring-cloud
    • Spring 中文社区https://spring.io/projects