一、微服务时代:为什么需要Spring Cloud?
1.1 从单体架构到微服务的演进
传统单体架构的痛点:
java
// 传统的单体应用结构(Monolithic Architecture)
monolithic-app/
├── src/main/java/com/example/monolithic/
│ ├── controller/ # 所有控制器
│ ├── service/ # 所有业务服务
│ ├── dao/ # 所有数据访问
│ └── entity/ # 所有实体类
├── src/main/resources/
│ ├── application.yml # 统一配置
│ └── static/ # 所有静态资源
└── pom.xml # 统一依赖管理
// 单体架构的问题:
- 代码库庞大,维护困难
- 技术栈单一,无法按需选型
- 发布风险高,牵一发而动全身
- 扩展性差,只能整体扩展
- 可靠性低,一个模块崩溃影响整个系统
微服务架构的优势:
java
// 微服务架构(Microservices Architecture)
microservices-system/
├── user-service/ # 用户服务(独立应用)
│ ├── src/main/java/com/example/user/
│ ├── pom.xml
│ └── Dockerfile
├── product-service/ # 商品服务
├── order-service/ # 订单服务
├── payment-service/ # 支付服务
├── gateway-service/ # API网关
└── config-service/ # 配置中心
// 微服务的优势:
- 独立开发部署,团队自治
- 技术栈灵活,各服务可用不同技术
- 容错性强,单点故障不影响整体
- 扩展性好,可按需扩展热点服务
- 持续交付,快速迭代
1.2 Spring Cloud生态全景图
Spring Cloud技术栈全景:
text
Spring Cloud 核心组件架构:
服务治理层
├── Service Discovery (服务发现)
│ ├── Eureka (Netflix) # 已停止维护
│ ├── Consul (HashiCorp) # 推荐
│ └── Nacos (Alibaba) # 强烈推荐
│
├── Service Registration (服务注册)
│ └── 各服务向注册中心注册自己
│
└── Service Invocation (服务调用)
├── RestTemplate (传统方式)
├── Feign (声明式HTTP客户端)
└── OpenFeign (推荐)
配置管理层
├── Config Server (配置中心)
│ ├── Spring Cloud Config # 原生
│ └── Nacos Config # 推荐
│
└── Config Client (配置客户端)
└── 各服务从配置中心获取配置
流量控制层
├── API Gateway (API网关)
│ ├── Spring Cloud Gateway # 推荐 (WebFlux)
│ └── Zuul (Netflix) # 旧版
│
├── Load Balancer (负载均衡)
│ ├── Ribbon (Netflix) # 客户端负载均衡
│ └── Spring Cloud LoadBalancer # 推荐
│
└── Circuit Breaker (熔断器)
├── Hystrix (Netflix) # 已停止维护
├── Resilience4j # 推荐
└── Sentinel (Alibaba) # 推荐
监控追踪层
├── Distributed Tracing (分布式追踪)
│ ├── Sleuth (链路追踪)
│ └── Zipkin (数据收集展示)
│
├── Metrics (指标监控)
│ ├── Micrometer (指标门面)
│ └── Prometheus (收集存储)
│
└── Monitoring (监控告警)
└── Spring Boot Admin # 监控面板
消息驱动层
├── Message Broker (消息代理)
│ ├── RabbitMQ
│ └── Kafka
│
└── Stream (消息驱动)
└── Spring Cloud Stream
安全控制层
├── Security (安全认证)
│ └── Spring Cloud Security
│
└── OAuth2 (授权认证)
└── Spring Security OAuth2
Spring Cloud版本选择指南:
xml
Spring Cloud 2022.0.x (代号Kilburn) → Spring Boot 3.0.x (推荐)
Spring Cloud 2021.0.x (代号Jubilee) → Spring Boot 2.6.x/2.7.x
Spring Cloud 2020.0.x (代号Ilford) → Spring Boot 2.4.x/2.5.x
Spring Cloud Hoxton.SR12 → Spring Boot 2.3.x/2.4.x
二、环境准备与第一个微服务
2.1 微服务开发环境全配置
开发环境要求:
bash
1. Java环境
java -version # 需要 Java 17+ (Spring Cloud 2022+)
或 Java 8-17 (Spring Cloud 2021-)
2. Maven环境
mvn -v # Maven 3.6+
3. Docker环境 (可选,用于服务容器化)
docker -v
4. IDE配置
IntelliJ IDEA推荐插件:
- Spring Boot Assistant
- Lombok
- Maven Helper
- MyBatisX
5. 网络工具
- Postman (API测试)
- Redis Desktop Manager
- Nacos (服务注册中心)
创建父工程(微服务聚合项目):
xml
<?xml version="1.0" encoding="UTF-8"?>
<modelVersion>4.0.0</modelVersion>
<!-- 父工程信息 -->
<groupId>com.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>spring-cloud-demo</name>
<description>Spring Cloud微服务演示项目</description>
<!-- 模块定义 -->
<modules>
<module>eureka-server</module>
<module>nacos-server</module>
<module>config-server</module>
<module>gateway-service</module>
<module>user-service</module>
<module>product-service</module>
<module>order-service</module>
</modules>
<!-- 统一版本管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.2</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- 依赖管理 -->
<dependencyManagement>
<dependencies>
<!-- 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>
</dependencies>
</dependencyManagement>
<!-- 通用依赖 -->
<dependencies>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 构建配置 -->
<build>
<plugins>
<!-- 编译器插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- Spring Boot Maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
2.2 第一个微服务:用户服务 创建用户服务模块:
bash
在父工程目录下创建用户服务模块
mkdir user-service
cd user-service
用户服务pom.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>user-service</artifactId>
<name>user-service</name>
<description>用户服务</description>
<properties>
<!-- 服务端口 -->
<service.port>8081</service.port>
</properties>
<dependencies>
<!-- Web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 服务发现客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 配置客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- 健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- OpenFeign服务调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 数据访问 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 数据验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Redis缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Swagger API文档 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
用户服务启动类:
java
package com.example.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient // 启用服务发现客户端
@EnableFeignClients // 启用Feign客户端
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
System.out.println("\n=========================================");
System.out.println("用户服务启动成功!");
System.out.println("端口: 8081");
System.out.println("Swagger文档: http://localhost:8081/swagger-ui.html");
System.out.println("健康检查: http://localhost:8081/actuator/health");
System.out.println("=========================================\n");
}
}
用户服务配置文件:
yaml
application.yml
server:
port: 8081
servlet:
context-path: /user-service
spring:
application:
name: user-service # 服务名称(重要!)
数据源配置
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
JPA配置
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
Redis配置
redis:
host: localhost
port: 6379
password:
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
配置中心(这里先使用本地配置,后面会改)
cloud:
config:
enabled: false
Eureka客户端配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ # Eureka服务器地址
register-with-eureka: true # 注册到Eureka
fetch-registry: true # 从Eureka获取注册表
registry-fetch-interval-seconds: 30 # 刷新注册表间隔
instance:
instance-id: spring.application.name:{spring.application.name}:spring.application.name:{server.port} # 实例ID
prefer-ip-address: true # 使用IP地址注册
ip-address: 127.0.0.1 # IP地址
lease-renewal-interval-in-seconds: 30 # 续约间隔
lease-expiration-duration-in-seconds: 90 # 过期时间
管理端点
management:
endpoints:
web:
exposure:
include: "health,info,metrics"
endpoint:
health:
show-details: always
日志配置
logging:
level:
com.example: DEBUG
org.springframework.cloud: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
Feign配置
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
用户服务核心代码:
java
// 1. 用户实体类
package com.example.userservice.entity;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "tb_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false, length = 100)
private String password;
@Column(nullable = false, unique = true, length = 100)
private String email;
@Column(length = 20)
private String phone;
@Column(length = 200)
private String avatar;
@Column(nullable = false)
private Integer status = 1; // 1-正常,2-禁用
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
}
// 2. 用户Repository
package com.example.userservice.repository;
import com.example.userservice.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
// 3. 用户DTO
package com.example.userservice.dto;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserDTO {
private Long id;
private String username;
private String email;
private String phone;
private String avatar;
private Integer status;
private LocalDateTime createTime;
}
// 4. 用户服务接口
package com.example.userservice.service;
import com.example.userservice.dto.UserDTO;
import java.util.List;
public interface UserService {
UserDTO getUserById(Long id);
UserDTO getUserByUsername(String username);
List<UserDTO> getAllUsers();
UserDTO createUser(UserCreateRequest request);
UserDTO updateUser(Long id, UserUpdateRequest request);
void deleteUser(Long id);
// 供其他服务调用的内部接口
UserDTO getUserInfo(Long userId);
}
// 5. 用户服务实现
package com.example.userservice.service.impl;
import com.example.userservice.dto.UserDTO;
import com.example.userservice.entity.User;
import com.example.userservice.repository.UserRepository;
import com.example.userservice.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
@Override
@Cacheable(value = "user", key = "#id")
public UserDTO getUserById(Long id) {
log.info("查询用户: id={}", id);
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
return userMapper.toDTO(user);
}
@Override
@Cacheable(value = "user", key = "'username:' + #username")
public UserDTO getUserByUsername(String username) {
log.info("查询用户: username={}", username);
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("用户不存在"));
return userMapper.toDTO(user);
}
@Override
public List<UserDTO> getAllUsers() {
log.info("查询所有用户");
return userRepository.findAll().stream()
.map(userMapper::toDTO)
.collect(Collectors.toList());
}
@Override
@Transactional
public UserDTO createUser(UserCreateRequest request) {
log.info("创建用户: username={}", request.getUsername());
// 检查用户名是否存在
if (userRepository.existsByUsername(request.getUsername())) {
throw new RuntimeException("用户名已存在");
}
// 检查邮箱是否存在
if (userRepository.existsByEmail(request.getEmail())) {
throw new RuntimeException("邮箱已存在");
}
User user = userMapper.toEntity(request);
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
user.setStatus(1);
user = userRepository.save(user);
return userMapper.toDTO(user);
}
@Override
public UserDTO getUserInfo(Long userId) {
log.info("内部调用: 获取用户信息, userId={}", userId);
return getUserById(userId);
}
}
// 6. 用户控制器
package com.example.userservice.controller;
import com.example.userservice.dto.UserDTO;
import com.example.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.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户管理相关接口")
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
@Operation(summary = "根据ID获取用户", description = "根据用户ID获取用户详细信息")
public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
UserDTO user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
@GetMapping("/username/{username}")
@Operation(summary = "根据用户名获取用户", description = "根据用户名获取用户详细信息")
public ResponseEntity<UserDTO> getUserByUsername(@PathVariable String username) {
UserDTO user = userService.getUserByUsername(username);
return ResponseEntity.ok(user);
}
@GetMapping
@Operation(summary = "获取所有用户", description = "获取所有用户列表")
public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
@GetMapping("/internal/{userId}")
@Operation(summary = "内部接口-获取用户信息", description = "供其他微服务调用的内部接口")
public ResponseEntity<UserDTO> getUserInfo(@PathVariable Long userId) {
log.info("收到内部服务调用: userId={}", userId);
UserDTO user = userService.getUserInfo(userId);
return ResponseEntity.ok(user);
}
}
三、服务注册与发现:微服务的基石
3.1 Eureka服务注册中心
Eureka服务端配置:
xml
<?xml version="1.0" encoding="UTF-8"?> 4.0.0
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>eureka-server</artifactId>
<name>eureka-server</name>
<description>Eureka注册中心</description>
<properties>
<service.port>8761</service.port>
</properties>
<dependencies>
<!-- Eureka服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 安全认证(可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 监控端点 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
Eureka启动类:
java
package com.example.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // 启用Eureka服务端
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
System.out.println("\n=========================================");
System.out.println("Eureka注册中心启动成功!");
System.out.println("控制台: http://localhost:8761");
System.out.println("=========================================\n");
}
}
Eureka配置文件:
yaml
application.yml
server:
port: 8761
spring:
application:
name: eureka-server
安全配置(可选)
security:
user:
name: admin
password: 123456
Eureka服务器配置
eureka:
instance:
hostname: localhost
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90
server:
enable-self-preservation: true # 开启自我保护
renewal-percent-threshold: 0.85 # 续约百分比阈值
eviction-interval-timer-in-ms: 60000 # 清理间隔
response-cache-update-interval-ms: 30000 # 响应缓存更新间隔
client:
register-with-eureka: false # 不向自己注册
fetch-registry: false # 不从自己获取注册表
service-url:
defaultZone: http://eureka.instance.hostname:{eureka.instance.hostname}:eureka.instance.hostname:{server.port}/eureka/
管理端点
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
Eureka安全配置(可选):
java
package com.example.eurekaserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable() // 禁用CSRF,否则客户端无法注册
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/**").permitAll() // 监控端点公开
.anyRequest().authenticated() // 其他请求需要认证
)
.httpBasic(); // 使用基本认证
return http.build();
}
}
3.2 Nacos服务注册中心(推荐)
为什么选择Nacos?
text
Eureka vs Nacos 对比:
| 特性 | Eureka | Nacos |
|---|---|---|
| 服务发现 | ✅ | ✅ |
| 配置管理 | ❌ | ✅ |
| 健康检查 | 客户端心跳 | 客户端/服务端心跳 |
| 负载均衡 | 配合Ribbon | 内置 |
| 动态配置 | ❌ | ✅ |
| 服务元数据 | 有限 | 丰富 |
| 集群模式 | 复杂 | 简单 |
| 管理界面 | 简单 | 丰富 |
| 社区活跃 | 停止维护 | 活跃 |
结论:新项目推荐使用Nacos!
Nacos服务器安装:
bash
1. 下载Nacos(推荐2.x版本)
wget https://github.com/alibaba/nacos/releases/download/2.2.0/nacos-server-2.2.0.tar.gz
2. 解压
tar -zxvf nacos-server-2.2.0.tar.gz
cd nacos
3. 启动(单机模式)
Linux/Mac:
sh bin/startup.sh -m standalone
Windows:
cmd bin/startup.cmd
4. 访问控制台
http://localhost:8848/nacos
用户名:nacos
密码:nacos
使用Docker运行Nacos:
bash
使用Docker Compose运行Nacos + MySQL
version: '3.8'
services:
nacos:
image: nacos/nacos-server:v2.2.0
container_name: nacos-server
environment:
-
MODE=standalone # 单机模式
-
SPRING_DATASOURCE_PLATFORM=mysql
-
MYSQL_SERVICE_HOST=mysql
-
MYSQL_SERVICE_DB_NAME=nacos
-
MYSQL_SERVICE_PORT=3306
-
MYSQL_SERVICE_USER=root
-
MYSQL_SERVICE_PASSWORD=123456
-
NACOS_AUTH_ENABLE=true # 开启认证
ports:
-
"8848:8848"
-
"9848:9848"
-
"9849:9849"
volumes:
- nacos_logs:/home/nacos/logs
depends_on:
- mysql
networks:
- nacos-network
mysql:
image: mysql:8.0
container_name: nacos-mysql
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: nacos
ports:
- "3306:3306"
volumes:
-
mysql_data:/var/lib/mysql
-
./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- nacos-network
volumes:
nacos_logs:
mysql_data:
networks:
nacos-network:
driver: bridge
Nacos客户端配置:
xml
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config Nacos客户端配置:
yaml
bootstrap.yml (优先级高于application.yml)
spring:
application:
name: user-service
Nacos配置中心
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos服务器地址
namespace: public # 命名空间
group: DEFAULT_GROUP # 分组
cluster-name: DEFAULT # 集群名称
metadata:
version: 1.0.0 # 元数据
author: spring-cloud-user
心跳配置
heart-beat-interval: 5000 # 心跳间隔(毫秒)
heart-beat-timeout: 15000 # 心跳超时时间
ip-delete-timeout: 30000 # IP删除超时时间
config:
server-addr: localhost:8848 # 配置中心地址
namespace: public
group: DEFAULT_GROUP
file-extension: yaml # 配置文件扩展名
# 配置自动刷新
refresh-enabled: true
# 共享配置
shared-configs:
- data-id: common.yaml
group: DEFAULT_GROUP
refresh: true
- data-id: datasource.yaml
group: DEFAULT_GROUP
refresh: true
extension-configs:
- data-id: redis.yaml
group: DEFAULT_GROUP
refresh: true
开启配置刷新
management:
endpoints:
web:
exposure:
include: "refresh,health,info"
3.3 服务注册与发现的实践
多实例用户服务配置:
yaml
用户服务实例1:application-user1.yml
server:
port: 8081
spring:
application:
name: user-service
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
用户服务实例2:application-user2.yml
server:
port: 8082
spring:
application:
name: user-service # 相同的服务名称!
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
启动多个实例:
bash
启动第一个实例
java -jar user-service-1.0.0.jar --server.port=8081 --spring.profiles.active=user1
启动第二个实例
java -jar user-service-1.0.0.jar --server.port=8082 --spring.profiles.active=user2
或者在IDEA中配置多个启动配置
服务发现客户端示例:
java
package com.example.userservice.discovery;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class ServiceDiscoveryClient {
private final DiscoveryClient discoveryClient;
/**
* 获取服务的所有实例
*/
public List<ServiceInstance> getServiceInstances(String serviceName) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
log.info("发现服务 {} 的实例: {}", serviceName, instances.size());
instances.forEach(instance -> {
log.info("实例: {}:{}",
instance.getHost(),
instance.getPort());
});
return instances;
}
/**
* 获取服务的第一个实例
*/
public ServiceInstance getFirstInstance(String serviceName) {
List<ServiceInstance> instances = getServiceInstances(serviceName);
if (instances.isEmpty()) {
throw new RuntimeException("服务 " + serviceName + " 没有可用实例");
}
return instances.get(0);
}
/**
* 获取所有注册的服务
*/
public List<String> getAllServices() {
List<String> services = discoveryClient.getServices();
log.info("发现 {} 个服务: {}", services.size(), services);
return services;
}
}
// 在控制器中使用
@RestController
@RequestMapping("/discovery")
@RequiredArgsConstructor
public class DiscoveryController {
private final ServiceDiscoveryClient discoveryClient;
@GetMapping("/services")
public List<String> getAllServices() {
return discoveryClient.getAllServices();
}
@GetMapping("/instances/{serviceName}")
public List<ServiceInstance> getServiceInstances(@PathVariable String serviceName) {
return discoveryClient.getServiceInstances(serviceName);
}
}
四、服务通信:微服务间如何对话
4.1 RestTemplate:传统的服务调用
配置负载均衡的RestTemplate:
java
package com.example.userservice.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// 设置连接超时和读取超时
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000); // 连接超时5秒
factory.setReadTimeout(10000); // 读取超时10秒
restTemplate.setRequestFactory(factory);
// 添加拦截器(可选)
restTemplate.setInterceptors(Collections.singletonList(
new RestTemplateInterceptor()
));
return restTemplate;
}
}
// 拦截器示例
@Component
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// 添加请求头
request.getHeaders().add("X-Request-Source", "user-service");
request.getHeaders().add("X-Request-ID", UUID.randomUUID().toString());
log.info("RestTemplate请求: {} {}, Headers: {}",
request.getMethod(), request.getURI(), request.getHeaders());
long startTime = System.currentTimeMillis();
ClientHttpResponse response = execution.execute(request, body);
long duration = System.currentTimeMillis() - startTime;
log.info("RestTemplate响应: {}, 耗时: {}ms",
response.getStatusCode(), duration);
return response;
}
}
使用RestTemplate调用其他服务:
java
@Service
@RequiredArgsConstructor
@Slf4j
public class ProductServiceClient {
private final RestTemplate restTemplate;
/**
* 调用商品服务的内部接口
* 使用服务名而不是具体IP地址
*/
public ProductDTO getProductById(Long productId) {
String url = "http://product-service/api/products/internal/" + productId;
try {
ResponseEntity<ProductDTO> response = restTemplate.exchange(
url,
HttpMethod.GET,
null,
ProductDTO.class
);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
return response.getBody();
} else {
throw new RuntimeException("获取商品信息失败: " + response.getStatusCode());
}
} catch (Exception e) {
log.error("调用商品服务失败: productId={}, error={}", productId, e.getMessage(), e);
throw new RuntimeException("商品服务调用失败", e);
}
}
/**
* 调用商品服务的公共接口(带负载均衡)
*/
public List<ProductDTO> getProductsByUserId(Long userId) {
String url = "http://product-service/api/products/user/" + userId;
ResponseEntity<List<ProductDTO>> response = restTemplate.exchange(
url,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ProductDTO>>() {}
);
return response.getBody();
}
/**
* 带重试机制的调用
*/
@Retryable(value = {ResourceAccessException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2))
public ProductDTO getProductWithRetry(Long productId) {
return getProductById(productId);
}
}
4.2 OpenFeign:声明式服务调用
Feign客户端配置:
java
package com.example.userservice.feign;
import com.example.userservice.dto.ProductDTO;
import com.example.userservice.feign.fallback.ProductServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
// 1. 基本Feign客户端
@FeignClient(name = "product-service",
path = "/api/products")
public interface ProductServiceClient {
@GetMapping("/{id}")
ProductDTO getProductById(@PathVariable("id") Long id);
@GetMapping("/user/{userId}")
List<ProductDTO> getProductsByUserId(@PathVariable("userId") Long userId);
@PostMapping
ProductDTO createProduct(@RequestBody ProductCreateRequest request);
}
// 2. 带降级处理的Feign客户端
@FeignClient(name = "product-service",
path = "/api/products",
fallbackFactory = ProductServiceFallbackFactory.class) // 降级工厂
public interface ProductServiceWithFallbackClient {
@GetMapping("/{id}")
ProductDTO getProductById(@PathVariable("id") Long id);
}
// 3. 带自定义配置的Feign客户端
@FeignClient(name = "product-service",
path = "/api/products",
configuration = ProductFeignConfig.class) // 自定义配置
public interface ProductServiceWithConfigClient {
@GetMapping("/{id}")
ProductDTO getProductById(@PathVariable("id") Long id);
}
Feign配置类:
java
package com.example.userservice.feign.config;
import feign.Logger;
import feign.Request;
import feign.Retryer;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
public class ProductFeignConfig {
/**
* Feign日志级别
* NONE: 不记录日志
* BASIC: 仅记录请求方法和URL以及响应状态码和执行时间
* HEADERS: 记录BASIC级别的基础上,还记录请求和响应的header
* FULL: 记录请求和响应的header,body和元数据
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
* 超时配置
*/
@Bean
public Request.Options options() {
return new Request.Options(
5, TimeUnit.SECONDS, // 连接超时时间
10, TimeUnit.SECONDS, // 读取超时时间
true // 跟随重定向
);
}
/**
* 重试配置
*/
@Bean
public Retryer feignRetryer() {
// 最大重试次数为3,初始间隔100ms,下次间隔乘以1.5
return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 3);
}
/**
* 错误解码器
*/
@Bean
public ErrorDecoder errorDecoder() {
return new ProductErrorDecoder();
}
}
// 自定义错误解码器
public class ProductErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
log.error("Feign调用失败: method={}, status={}", methodKey, response.status());
if (response.status() >= 400 && response.status() <= 499) {
return new RuntimeException("客户端错误: " + response.status());
}
if (response.status() >= 500 && response.status() <= 599) {
return new RuntimeException("服务端错误: " + response.status());
}
return new RuntimeException("未知错误");
}
}
Feign降级处理:
java
// 1. 降级工厂(推荐)
@Component
@Slf4j
public class ProductServiceFallbackFactory implements FallbackFactory {
@Override
public ProductServiceWithFallbackClient create(Throwable cause) {
return new ProductServiceWithFallbackClient() {
@Override
public ProductDTO getProductById(Long id) {
log.error("商品服务调用失败,执行降级逻辑: productId={}, error={}", id, cause.getMessage());
// 返回降级数据
ProductDTO product = new ProductDTO();
product.setId(id);
product.setName("商品服务暂时不可用");
product.setPrice(0.0);
product.setStatus(0);
product.setDescription("服务降级,请稍后重试");
return product;
}
};
}
}
// 2. 简单的降级类
@Component
public class ProductServiceFallback implements ProductServiceClient {
@Override
public ProductDTO getProductById(Long id) {
log.warn("商品服务降级: productId={}", id);
ProductDTO product = new ProductDTO();
product.setId(id);
product.setName("默认商品");
product.setPrice(0.0);
return product;
}
@Override
public List<ProductDTO> getProductsByUserId(Long userId) {
return Collections.emptyList();
}
@Override
public ProductDTO createProduct(ProductCreateRequest request) {
throw new RuntimeException("商品服务不可用");
}
}
全局Feign配置:
yaml
application.yml中的Feign配置
feign:
client:
config:
全局默认配置
default:
connectTimeout: 5000
readTimeout: 10000
loggerLevel: basic
retryer: com.example.userservice.feign.CustomRetryer
errorDecoder: com.example.userservice.feign.CustomErrorDecoder
解码器
decoder: org.springframework.cloud.openfeign.support.ResponseEntityDecoder
encoder: org.springframework.cloud.openfeign.support.SpringEncoder
contract: org.springframework.cloud.openfeign.support.SpringMvcContract
# 针对特定服务的配置
product-service:
connectTimeout: 3000
readTimeout: 5000
loggerLevel: full
order-service:
connectTimeout: 10000
readTimeout: 30000
压缩配置
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
启用Hystrix(如果需要)
hystrix:
enabled: false # Spring Cloud 2020+ 已移除Hystrix,使用Resilience4j替代
OkHttp客户端(替代默认的HttpURLConnection)
okhttp:
enabled: true
HTTP客户端连接池
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
time-to-live: 900000 # 15分钟
4.3 服务调用最佳实践
- 统一的请求/响应拦截器
java
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 添加统一的请求头
template.header("X-Request-ID", UUID.randomUUID().toString());
template.header("X-Request-Source", "user-service");
template.header("X-Request-Time", String.valueOf(System.currentTimeMillis()));
// 添加认证信息
SecurityContext context = SecurityContextHolder.getContext();
if (context != null && context.getAuthentication() != null) {
Object principal = context.getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails) principal).getUsername();
template.header("X-User-Name", username);
}
}
log.debug("Feign请求: {} {}, Headers: {}",
template.method(), template.url(), template.headers());
}
}
- 统一的响应处理
java
@Configuration
public class FeignConfig {
@Bean
public Decoder feignDecoder() {
ObjectFactory<HttpMessageConverters> messageConverters = () -> {
HttpMessageConverters converters = new HttpMessageConverters();
// 添加自定义的消息转换器
return converters;
};
return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
}
@Bean
public Encoder feignEncoder() {
return new SpringEncoder(new SpringFactory(new EmptyObjectFactory<>()));
}
}
// 统一的响应DTO
@Data
@Builder
public class ApiResponse {
private Integer code;
private String message;
private T data;
private Long timestamp;
public static <T> ApiResponse<T> success(T data) {
return ApiResponse.<T>builder()
.code(200)
.message("成功")
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
}
- 调用链跟踪
java
@Component
public class FeignTraceInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 获取当前Span(如果使用Sleuth)
String traceId = getTraceId();
if (traceId != null) {
template.header("X-B3-TraceId", traceId);
template.header("X-B3-SpanId", getSpanId());
template.header("X-B3-ParentSpanId", getParentSpanId());
template.header("X-B3-Sampled", "1");
}
}
private String getTraceId() {
// 从MDC或ThreadLocal中获取
return MDC.get("traceId");
}
private String getSpanId() {
return MDC.get("spanId");
}
private String getParentSpanId() {
return MDC.get("parentSpanId");
}
}
五、API网关:微服务的守门人
5.1 Spring Cloud Gateway入门
为什么需要API网关?
text
网关的核心功能:
- 统一入口:所有外部请求都通过网关
- 路由转发:根据规则转发到具体服务
- 负载均衡:在多个服务实例间分配请求
- 安全认证:统一认证和授权
- 限流熔断:保护后端服务
- 日志监控:统一收集请求日志
- 跨域处理:统一处理CORS
创建网关服务:
xml
<?xml version="1.0" encoding="UTF-8"?> 4.0.0
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>gateway-service</artifactId>
<name>gateway-service</name>
<description>API网关服务</description>
<properties>
<service.port>8080</service.port>
</properties>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 服务发现 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Nacos服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<!-- 安全认证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT支持 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- 配置中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- 监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
</dependencies>
网关启动类:
java
package com.example.gatewayservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
System.out.println("\n=========================================");
System.out.println("API网关启动成功!");
System.out.println("网关地址: http://localhost:8080");
System.out.println("用户服务: http://localhost:8080/user-service/**");
System.out.println("商品服务: http://localhost:8080/product-service/**");
System.out.println("订单服务: http://localhost:8080/order-service/**");
System.out.println("=========================================\n");
}
}
5.2 网关路由配置
静态路由配置:
yaml
application.yml
server:
port: 8080
spring:
application:
name: gateway-service
cloud:
gateway:
开启服务发现的路由
discovery:
locator:
enabled: true
lower-case-service-id: true # 服务ID小写
# 全局默认过滤器
default-filters:
- AddRequestHeader=X-Gateway-Request, gateway-service
- AddResponseHeader=X-Gateway-Response, processed-by-gateway
# 路由配置
routes:
# 用户服务路由
- id: user-service-route
uri: lb://user-service # lb:// 表示负载均衡
predicates:
- Path=/api/users/** # 路径匹配
filters:
- StripPrefix=1 # 去掉前缀 /api
- name: RequestRateLimiter # 限流过滤器
args:
redis-rate-limiter.replenishRate: 10 # 每秒令牌数
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
key-resolver: "#{@userKeyResolver}" # 限流键解析器
- name: CircuitBreaker # 断路器
args:
name: userServiceCircuitBreaker
fallbackUri: forward:/fallback/user-service
# 商品服务路由
- id: product-service-route
uri: lb://product-service
predicates:
- Path=/api/products/**
- Method=GET,POST # 方法匹配
filters:
- StripPrefix=1
- RewritePath=/api/products/(?<segment>.*), /$\{segment} # 路径重写
# 订单服务路由
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/orders/**
- After=2023-01-01T00:00:00.000+08:00[Asia/Shanghai] # 时间匹配
filters:
- StripPrefix=1
# 静态资源路由
- id: static-resource-route
uri: http://localhost:8081 # 直接URL
predicates:
- Path=/static/**
# 重定向路由
- id: redirect-route
uri: https://example.com
predicates:
- Path=/redirect/**
filters:
- RedirectTo=302, https://www.baidu.com # 重定向
# 权重路由
- id: weight-high
uri: lb://user-service
predicates:
- Path=/api/v2/users/**
- Weight=group1, 80 # 80%流量
filters:
- StripPrefix=2
- id: weight-low
uri: lb://user-service-v2
predicates:
- Path=/api/v2/users/**
- Weight=group1, 20 # 20%流量
filters:
- StripPrefix=2
# 自定义断言路由
- id: custom-predicate-route
uri: lb://user-service
predicates:
- Path=/api/custom/**
- name: CustomRoutePredicateFactory # 自定义断言
args:
minId: 1
maxId: 100
filters:
- StripPrefix=1
# 负载均衡配置
loadbalancer:
enabled: true
configurations: zone-preference
# 断路器配置
circuitbreaker:
resilience4j:
instances:
userServiceCircuitBreaker:
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
Redis配置(用于限流)
redis:
host: localhost
port: 6379
timeout: 2000ms
管理端点
management:
endpoints:
web:
exposure:
include: "gateway,health,info,metrics"
endpoint:
gateway:
enabled: true
metrics:
export:
prometheus:
enabled: true
动态路由配置(数据库存储):
java
// 1. 路由定义实体
@Data
@Entity
@Table(name = "gateway_route")
public class GatewayRoute {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String routeId;
@Column(nullable = false)
private String uri;
@Column(name = "predicates", length = 2000)
private String predicates; // JSON格式
@Column(name = "filters", length = 2000)
private String filters; // JSON格式
@Column(nullable = false)
private Integer order = 0;
@Column(length = 500)
private String description;
@Column(nullable = false)
private Boolean enabled = true;
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
}
// 2. 动态路由服务
@Service
@Slf4j
@RequiredArgsConstructor
public class DynamicRouteService {
private final RouteDefinitionWriter routeDefinitionWriter;
private final ApplicationEventPublisher publisher;
private final GatewayRouteRepository routeRepository;
/**
* 从数据库加载路由配置
*/
@PostConstruct
public void initRoutes() {
log.info("初始化动态路由...");
List<GatewayRoute> routes = routeRepository.findByEnabledTrue();
routes.forEach(this::addRoute);
log.info("动态路由初始化完成,加载了 {} 条路由", routes.size());
}
/**
* 添加路由
*/
public void addRoute(GatewayRoute gatewayRoute) {
RouteDefinition definition = new RouteDefinition();
definition.setId(gatewayRoute.getRouteId());
definition.setUri(URI.create(gatewayRoute.getUri()));
definition.setOrder(gatewayRoute.getOrder());
// 解析断言
List<PredicateDefinition> predicateDefinitions =
JSON.parseArray(gatewayRoute.getPredicates(), PredicateDefinition.class);
definition.setPredicates(predicateDefinitions);
// 解析过滤器
List<FilterDefinition> filterDefinitions =
JSON.parseArray(gatewayRoute.getFilters(), FilterDefinition.class);
definition.setFilters(filterDefinitions);
// 添加路由
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
// 发布路由刷新事件
publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("添加动态路由: {}", gatewayRoute.getRouteId());
}
/**
* 删除路由
*/
public void deleteRoute(String routeId) {
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("删除动态路由: {}", routeId);
}
/**
* 更新路由
*/
public void updateRoute(GatewayRoute gatewayRoute) {
deleteRoute(gatewayRoute.getRouteId());
addRoute(gatewayRoute);
log.info("更新动态路由: {}", gatewayRoute.getRouteId());
}
/**
* 刷新路由
*/
public void refreshRoutes() {
publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("刷新所有路由");
}
}
5.3 网关过滤器
全局过滤器:
java
// 1. 认证过滤器
@Component
@Order(-1) // 优先级,数字越小优先级越高
@Slf4j
public class AuthenticationFilter implements GlobalFilter {
private final JwtTokenUtil jwtTokenUtil;
private final RedisTemplate<String, Object> redisTemplate;
// 白名单路径
private static final List<String> WHITE_LIST = Arrays.asList(
"/api/users/login",
"/api/users/register",
"/actuator/health",
"/swagger-ui",
"/v3/api-docs"
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().value();
// 检查是否是白名单路径
if (isWhiteList(path)) {
return chain.filter(exchange);
}
// 获取token
String token = getToken(request);
if (StringUtils.isEmpty(token)) {
return unauthorized(exchange, "缺少认证令牌");
}
// 验证token
if (!jwtTokenUtil.validateToken(token)) {
return unauthorized(exchange, "令牌无效或已过期");
}
// 检查token是否在黑名单中(用户注销)
if (isTokenBlacklisted(token)) {
return unauthorized(exchange, "令牌已失效");
}
// 获取用户信息
String username = jwtTokenUtil.getUsernameFromToken(token);
List<String> authorities = jwtTokenUtil.getAuthoritiesFromToken(token);
// 将用户信息添加到请求头
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-Name", username)
.header("X-User-Authorities", String.join(",", authorities))
.build();
// 记录访问日志
logAccess(request, username);
return chain.filter(exchange.mutate().request(mutatedRequest).build());
}
private boolean isWhiteList(String path) {
return WHITE_LIST.stream().anyMatch(path::startsWith);
}
private String getToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private boolean isTokenBlacklisted(String token) {
String key = "blacklist:token:" + DigestUtils.md5DigestAsHex(token.getBytes());
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
private void logAccess(ServerHttpRequest request, String username) {
log.info("网关认证通过: user={}, method={}, path={}, ip={}",
username,
request.getMethod(),
request.getPath(),
request.getRemoteAddress());
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
ApiResponse<?> apiResponse = ApiResponse.error(401, message);
DataBuffer buffer = response.bufferFactory()
.wrap(JSON.toJSONBytes(apiResponse));
return response.writeWith(Mono.just(buffer));
}
}
// 2. 日志记录过滤器
@Component
@Order(0)
@Slf4j
public class LoggingFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
long startTime = System.currentTimeMillis();
String requestId = UUID.randomUUID().toString();
exchange.getAttributes().put("REQUEST_ID", requestId);
log.info("请求开始: id={}, method={}, path={}, query={}, ip={}, headers={}",
requestId,
request.getMethod(),
request.getPath(),
request.getQueryParams(),
request.getRemoteAddress(),
request.getHeaders());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
long duration = System.currentTimeMillis() - startTime;
log.info("请求完成: id={}, status={}, duration={}ms",
requestId,
response.getStatusCode(),
duration);
}));
}
}
// 3. 限流过滤器
@Component
@Order(1)
public class RateLimitFilter implements GlobalFilter {
private final RateLimiter rateLimiter;
public RateLimitFilter() {
// 每秒10个请求
this.rateLimiter = RateLimiter.create(10.0);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String ip = getClientIp(request);
// 检查是否超过限流
if (!rateLimiter.tryAcquire()) {
log.warn("请求被限流: ip={}, path={}", ip, request.getPath());
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
ApiResponse<?> apiResponse = ApiResponse.error(429, "请求过于频繁,请稍后重试");
DataBuffer buffer = response.bufferFactory()
.wrap(JSON.toJSONBytes(apiResponse));
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
private String getClientIp(ServerHttpRequest request) {
String ip = request.getHeaders().getFirst("X-Forwarded-For");
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeaders().getFirst("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddress() != null ?
request.getRemoteAddress().getAddress().getHostAddress() : "";
}
return ip;
}
}
自定义网关过滤器:
java
// 1. 自定义过滤器工厂
@Component
public class CustomGatewayFilterFactory extends
AbstractGatewayFilterFactory<CustomGatewayFilterFactory.Config> {
public CustomGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
log.info("自定义过滤器执行: path={}, config={}",
request.getPath(), config);
// 修改请求
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-Custom-Header", config.getHeaderValue())
.build();
// 修改响应
return chain.filter(exchange.mutate().request(mutatedRequest).build())
.then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("X-Custom-Response", "processed");
}));
};
}
@Data
public static class Config {
private String headerValue = "custom-value";
private Boolean enabled = true;
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("headerValue", "enabled");
}
}
// 2. 使用自定义过滤器
application.yml
spring:
cloud:
gateway:
routes:
- id: custom-filter-route
uri: lb://user-service
predicates:
- Path=/api/custom/**
filters:
-
StripPrefix=1
-
name: Custom # 自定义过滤器
args:
headerValue: "from-custom-filter"
enabled: true
六、配置中心:统一管理微服务配置
6.1 Spring Cloud Config
配置服务器:
xml
org.springframework.cloud spring-cloud-config-server org.springframework.cloud spring-cloud-starter-netflix-eureka-client 配置服务器启动类:
java
@SpringBootApplication
@EnableConfigServer // 启用配置服务器
@EnableDiscoveryClient
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
配置服务器配置:
yaml
config-server/application.yml
server:
port: 8888
spring:
application:
name: config-server
配置存储方式:Git、SVN、本地文件系统等
cloud:
config:
server:
git:
uri: https://github.com/yourusername/config-repo
search-paths: '{application}' # 按应用名搜索
username: ${GIT_USERNAME}
password: ${GIT_PASSWORD}
default-label: main
timeout: 10
本地文件系统
native:
search-locations: classpath:/config/{application}
# 加密解密配置
encrypt:
enabled: true
key: ${CONFIG_ENCRYPT_KEY:default-key}
安全配置
security:
user:
name: config
password: config123
服务注册
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
暴露端点
management:
endpoints:
web:
exposure:
include: "health,bus-refresh,encrypt,decrypt"
客户端配置:
yaml
bootstrap.yml (客户端)
spring:
application:
name: user-service # 应用名,对应配置文件名
cloud:
config:
uri: http://localhost:8888 # 配置服务器地址
name: ${spring.application.name} # 配置文件名
profile: ${spring.profiles.active:dev} # 环境
label: main # 分支
fail-fast: true # 快速失败
retry:
max-attempts: 6
initial-interval: 1000
max-interval: 2000
multiplier: 1.1
# 安全认证
username: config
password: config123
# 配置自动刷新
refresh:
enabled: true
开启配置刷新端点
management:
endpoints:
web:
exposure:
include: "refresh,health"
6.2 Nacos配置中心(推荐)
Nacos配置中心优势:
text
- 配置动态刷新:实时推送配置变更
- 多环境支持:namespace + group + dataId
- 配置版本管理:历史版本和回滚
- 配置监听:监听配置变化
- 配置权限:细粒度的权限控制
- 配置导入导出:批量操作
Nacos配置中心使用:
yaml
bootstrap.yml
spring:
application:
name: user-service
cloud:
nacos:
config:
server-addr: localhost:8848 # Nacos服务器地址
namespace: ${NACOS_NAMESPACE:public} # 命名空间
group: ${NACOS_GROUP:DEFAULT_GROUP} # 分组
file-extension: yaml # 配置文件扩展名
# 共享配置(多个服务共用的配置)
shared-configs:
- data-id: common.yaml # 公共配置
group: DEFAULT_GROUP
refresh: true
- data-id: datasource.yaml # 数据源配置
group: DEFAULT_GROUP
refresh: true
- data-id: redis.yaml # Redis配置
group: DEFAULT_GROUP
refresh: true
- data-id: logging.yaml # 日志配置
group: DEFAULT_GROUP
refresh: true
# 扩展配置
extension-configs:
- data-id: ${spring.application.name}-${spring.profiles.active}.yaml
group: DEFAULT_GROUP
refresh: true
- data-id: ${spring.application.name}.yaml
group: DEFAULT_GROUP
refresh: true
# 配置刷新
refresh-enabled: true
# 用户名密码(如果开启Nacos认证)
username: nacos
password: nacos
# 配置超时
timeout: 3000
config-long-poll-timeout: 30000
config-retry-time: 3000
max-retry: 3
enable-remote-sync-config: true
配置刷新监听器:
java
@Component
@Slf4j
@RefreshScope // 支持配置刷新
public class ConfigRefreshListener {
@Value("${app.config.name:default}")
private String configName;
@Value("${app.config.version:1.0.0}")
private String configVersion;
@PostConstruct
public void init() {
log.info("配置初始化: name={}, version={}", configName, configVersion);
}
/**
* 配置变更监听
*/
@EventListener
public void onConfigChange(RefreshScopeRefreshedEvent event) {
log.info("配置已刷新: name={}, version={}", configName, configVersion);
// 执行配置变更后的逻辑
refreshCache();
reloadBusinessConfig();
}
private void refreshCache() {
log.info("刷新缓存...");
// 清理本地缓存
}
private void reloadBusinessConfig() {
log.info("重新加载业务配置...");
// 重新加载业务配置
}
}
// 动态配置类
@Component
@ConfigurationProperties(prefix = "app.dynamic")
@RefreshScope
@Data
public class DynamicConfig {
private Boolean featureEnabled = false;
private Integer timeout = 5000;
private String apiUrl;
private List<String> whiteList = new ArrayList<>();
private Map<String, String> params = new HashMap<>();
@PostConstruct
public void init() {
log.info("动态配置加载: {}", this);
}
@EventListener
public void onRefresh(RefreshScopeRefreshedEvent event) {
log.info("动态配置已刷新: {}", this);
}
}
七、服务熔断与降级
7.1 Resilience4j断路器
Resilience4j依赖:
xml
org.springframework.cloud
spring-cloud-starter-circuitbreaker-reactor-resilience4j
断路器配置:
yaml
Resilience4j配置
resilience4j:
circuitbreaker:
instances:
userServiceCircuitBreaker:
slidingWindowSize: 10 # 滑动窗口大小
minimumNumberOfCalls: 5 # 最小调用次数
permittedNumberOfCallsInHalfOpenState: 3 # 半开状态允许的调用次数
automaticTransitionFromOpenToHalfOpenEnabled: true # 自动从打开转为半开
waitDurationInOpenState: 5s # 打开状态的等待时间
failureRateThreshold: 50 # 失败率阈值百分比
slowCallRateThreshold: 100 # 慢调用率阈值
slowCallDurationThreshold: 2s # 慢调用时间阈值
eventConsumerBufferSize: 10 # 事件缓冲区大小
productServiceCircuitBreaker:
slidingWindowType: TIME_BASED # 滑动窗口类型:TIME_BASED或COUNT_BASED
slidingWindowSize: 10
minimumNumberOfCalls: 10
waitDurationInOpenState: 10s
failureRateThreshold: 30
retry:
instances:
userServiceRetry:
maxAttempts: 3 # 最大重试次数
waitDuration: 1s # 重试等待时间
retryExceptions:
-
java.io.IOException
-
org.springframework.web.client.ResourceAccessException
ignoreExceptions:
- com.example.BusinessException
bulkhead:
instances:
userServiceBulkhead:
maxConcurrentCalls: 10 # 最大并发调用数
maxWaitDuration: 10ms # 最大等待时间
ratelimiter:
instances:
userServiceRateLimiter:
limitForPeriod: 10 # 周期内的限制次数
limitRefreshPeriod: 1s # 限制刷新周期
timeoutDuration: 5s # 超时时间
timelimiter:
instances:
userServiceTimeLimiter:
timeoutDuration: 3s # 超时时间
cancelRunningFuture: true # 是否取消正在运行的Future
断路器事件导出
management:
endpoints:
web:
exposure:
include: "circuitbreakers,health"
endpoint:
health:
show-details: always
circuitbreakers:
enabled: true
metrics:
export:
prometheus:
enabled: true
resilience4j:
circuitbreaker:
enabled: true
retry:
enabled: true
bulkhead:
enabled: true
ratelimiter:
enabled: true
使用断路器:
java
@Service
@Slf4j
@RequiredArgsConstructor
public class UserServiceWithCircuitBreaker {
private final CircuitBreakerRegistry circuitBreakerRegistry;
private final RetryRegistry retryRegistry;
private final BulkheadRegistry bulkheadRegistry;
private final ProductServiceClient productServiceClient;
/**
* 使用断路器调用商品服务
*/
public ProductDTO getProductWithCircuitBreaker(Long productId) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry
.circuitBreaker("productServiceCircuitBreaker");
Supplier<ProductDTO> supplier = () ->
productServiceClient.getProductById(productId);
Supplier<ProductDTO> decoratedSupplier = CircuitBreaker
.decorateSupplier(circuitBreaker, supplier);
try {
return decoratedSupplier.get();
} catch (Exception e) {
log.error("商品服务调用失败,断路器状态: {}",
circuitBreaker.getState(), e);
return getFallbackProduct(productId);
}
}
/**
* 组合使用断路器、重试和舱壁
*/
public ProductDTO getProductWithResilience(Long productId) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry
.circuitBreaker("productServiceCircuitBreaker");
Retry retry = retryRegistry.retry("productServiceRetry");
Bulkhead bulkhead = bulkheadRegistry
.bulkhead("productServiceBulkhead");
Supplier<ProductDTO> supplier = () ->
productServiceClient.getProductById(productId);
// 组合装饰器:舱壁 -> 重试 -> 断路器
Supplier<ProductDTO> decoratedSupplier = Decorators.ofSupplier(supplier)
.withBulkhead(bulkhead)
.withRetry(retry)
.withCircuitBreaker(circuitBreaker)
.withFallback(Arrays.asList(
CallNotPermittedException.class,
BulkheadFullException.class,
RuntimeException.class
), throwable -> getFallbackProduct(productId))
.decorate();
return decoratedSupplier.get();
}
/**
* 使用注解方式
*/
@CircuitBreaker(name = "productServiceCircuitBreaker",
fallbackMethod = "getProductFallback")
@Retry(name = "productServiceRetry",
fallbackMethod = "getProductFallback")
@Bulkhead(name = "productServiceBulkhead",
fallbackMethod = "getProductFallback")
@TimeLimiter(name = "productServiceTimeLimiter",
fallbackMethod = "getProductFallback")
public CompletableFuture<ProductDTO> getProductAsync(Long productId) {
return CompletableFuture.supplyAsync(() ->
productServiceClient.getProductById(productId));
}
/**
* 降级方法
*/
private ProductDTO getProductFallback(Long productId, Exception e) {
log.error("商品服务降级: productId={}, error={}", productId, e.getMessage());
ProductDTO product = new ProductDTO();
product.setId(productId);
product.setName("服务暂时不可用");
product.setDescription("商品服务降级处理");
product.setPrice(0.0);
product.setStatus(0);
return product;
}
private ProductDTO getFallbackProduct(Long productId) {
ProductDTO product = new ProductDTO();
product.setId(productId);
product.setName("降级商品");
product.setPrice(0.0);
return product;
}
}
// 断路器事件监听
@Component
@Slf4j
public class CircuitBreakerEventListener {
@EventListener
public void onStateChange(CircuitBreakerOnStateTransitionEvent event) {
log.info("断路器状态变更: {} -> {}, 断路器: {}",
event.getStateTransition().getFromState(),
event.getStateTransition().getToState(),
event.getCircuitBreakerName());
}
@EventListener
public void onError(CircuitBreakerOnErrorEvent event) {
log.error("断路器错误: 断路器={}, 错误={}",
event.getCircuitBreakerName(),
event.getThrowable().getMessage());
}
@EventListener
public void onSuccess(CircuitBreakerOnSuccessEvent event) {
log.debug("断路器成功调用: 断路器={}", event.getCircuitBreakerName());
}
@EventListener
public void onCallNotPermitted(CircuitBreakerOnCallNotPermittedEvent event) {
log.warn("断路器拒绝调用: 断路器={}", event.getCircuitBreakerName());
}
}
八、分布式链路追踪
8.1 Sleuth + Zipkin
添加依赖:
xml
org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin org.springframework.cloud spring-cloud-sleuth-stream Sleuth配置:
yaml
Sleuth配置
spring:
sleuth:
enabled: true
采样率:1.0表示100%采样,0.1表示10%采样
sampler:
probability: 1.0
# 传播配置
propagation:
type: B3 # B3, W3C, B3_MULTI
b3:
injection-format: MULTI # SINGLE, MULTI, MULTI_WITH_BAGGAGE
# 日志配置
log:
slf4j:
enabled: true
whitelisted-mdc-keys: traceId,spanId
# 异步配置
async:
enabled: true
default-async-acceptors: 2
# Web配置
web:
enabled: true
skip-pattern: /health,/actuator/health
# 消息中间件配置
messaging:
enabled: true
# Redis配置
redis:
enabled: true
# JDBC配置
jdbc:
enabled: true
Zipkin配置
zipkin:
base-url: http://localhost:9411 # Zipkin服务器地址
sender:
type: web # web, kafka, rabbit
service:
name: ${spring.application.name}
压缩配置
compression:
enabled: true
消息中间件配置
message-timeout: 1000
定位器配置
locator:
discovery:
enabled: true
日志配置(输出TraceID和SpanID)
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
手动创建Span:
java
@Service
@Slf4j
@RequiredArgsConstructor
public class TraceService {
private final Tracer tracer;
/**
* 手动创建Span
*/
public void manualTrace() {
// 创建新Span
Span newSpan = tracer.nextSpan().name("manualOperation").start();
try (Tracer.SpanInScope ws = tracer.withSpan(newSpan)) {
// 设置标签
newSpan.tag("operation", "manual");
newSpan.tag("user", "testUser");
// 记录事件
newSpan.event("operation.started");
// 执行业务逻辑
doBusinessLogic();
newSpan.event("operation.completed");
} catch (Exception e) {
// 记录错误
newSpan.error(e);
throw e;
} finally {
// 结束Span
newSpan.end();
}
}
/**
* 使用注解创建Span
*/
@NewSpan(name = "annotatedOperation")
public void annotatedTrace() {
doBusinessLogic();
}
/**
* 继续现有Span
*/
@ContinueSpan(log = "continueOperation")
public void continueTrace(@SpanTag("param") String param) {
doBusinessLogic();
}
private void doBusinessLogic() {
log.info("执行业务逻辑...");
// 模拟业务处理
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 自定义Span处理器
@Component
@Slf4j
public class CustomSpanHandler extends BraveSpanHandler {
@Override
public boolean end(TraceContext context, MutableSpan span, Cause cause) {
log.info("Span结束: traceId={}, spanId={}, name={}, duration={}ms",
span.traceId(),
span.id(),
span.name(),
span.finishTimestamp() - span.startTimestamp());
// 添加自定义标签
span.tag("custom.handler", "processed");
return super.end(context, span, cause);
}
}
Zipkin服务器部署:
bash
使用Docker运行Zipkin
docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin
或者使用Java运行
java -jar zipkin-server-2.24.0-exec.jar
访问Zipkin UI
http://localhost:9411/zipkin/
自定义Trace配置:
java
@Configuration
public class TraceConfig {
@Bean
public CurrentTraceContext currentTraceContext() {
return ThreadLocalCurrentTraceContext.newBuilder()
.addScopeDecorator(MDCScopeDecorator.create())
.build();
}
@Bean
public Sampler sampler() {
// 自定义采样策略
return new Sampler() {
@Override
public boolean isSampled(long traceId) {
// 根据traceId的奇偶性采样
return traceId % 2 == 0;
}
};
}
@Bean
public SpanHandler spanHandler() {
return new SpanHandler() {
@Override
public boolean end(TraceContext context, MutableSpan span, Cause cause) {
// 过滤不需要的Span
if (span.name().contains("health")) {
return false;
}
// 添加自定义标签
span.tag("service.env", System.getenv("ENV"));
span.tag("service.version", "1.0.0");
return true;
}
};
}
@Bean
public BaggagePropagation.FactoryBuilder baggagePropagationFactoryBuilder() {
return BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY)
.add(BaggagePropagationConfig.SingleBaggageField
.remote(BaggageField.create("user-id")))
.add(BaggagePropagationConfig.SingleBaggageField
.remote(BaggageField.create("session-id")));
}
}
九、微服务监控与告警
9.1 Spring Boot Actuator监控
Actuator配置:
yaml
management:
endpoints:
web:
exposure:
include: "*" # 暴露所有端点
base-path: /actuator # 端点基础路径
path-mapping:
health: healthcheck # 自定义路径
endpoint:
health:
show-details: always # 显示详细信息
probes:
enabled: true # 启用就绪性和存活性探针
group:
custom: # 自定义健康检查组
include: diskSpace,ping,customHealth
metrics:
enabled: true
prometheus:
enabled: true
info:
enabled: true
env:
enabled: true
loggers:
enabled: true
env:
enabled: true
beans:
enabled: true
mappings:
enabled: true
shutdown:
enabled: false # 生产环境建议禁用
metrics:
export:
prometheus:
enabled: true
influx:
enabled: false
tags:
application: ${spring.application.name}
instance: spring.cloud.client.ip−address:{spring.cloud.client.ip-address}:spring.cloud.client.ip−address:{server.port}
distribution:
percentiles-histogram:
http.server.requests: true
slo:
http.server.requests: 100ms, 200ms, 500ms
安全配置
security:
enabled: true
roles: ADMIN
自定义健康检查:
java
@Component
public class CustomHealthIndicator implements HealthIndicator {
private final RedisTemplate<String, Object> redisTemplate;
private final DataSource dataSource;
@Override
public Health health() {
Map<String, Object> details = new HashMap<>();
// 检查Redis连接
boolean redisHealthy = checkRedis();
details.put("redis", redisHealthy ? "UP" : "DOWN");
// 检查数据库连接
boolean dbHealthy = checkDatabase();
details.put("database", dbHealthy ? "UP" : "DOWN");
// 检查外部服务
boolean externalServiceHealthy = checkExternalService();
details.put("externalService", externalServiceHealthy ? "UP" : "DOWN");
// 计算总体状态
boolean overallHealthy = redisHealthy && dbHealthy && externalServiceHealthy;
if (overallHealthy) {
return Health.up()
.withDetails(details)
.build();
} else {
return Health.down()
.withDetails(details)
.build();
}
}
private boolean checkRedis() {
try {
redisTemplate.hasKey("health-check");
return true;
} catch (Exception e) {
return false;
}
}
private boolean checkDatabase() {
try (Connection conn = dataSource.getConnection()) {
return conn.isValid(5);
} catch (Exception e) {
return false;
}
}
private boolean checkExternalService() {
// 调用外部服务健康检查接口
try {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(
"http://external-service/health", String.class);
return response.getStatusCode().is2xxSuccessful();
} catch (Exception e) {
return false;
}
}
}
// 就绪性探针
@Component
public class ReadinessProbe implements ReadinessIndicator {
@Override
public Health getHealth(boolean includeDetails) {
// 检查应用是否准备好接收流量
boolean ready = checkDependencies();
if (ready) {
return Health.up()
.withDetail("status", "READY")
.build();
} else {
return Health.down()
.withDetail("status", "NOT_READY")
.withDetail("reason", "依赖服务未就绪")
.build();
}
}
private boolean checkDependencies() {
// 检查所有依赖是否就绪
return checkDatabase() && checkCache() && checkMessageQueue();
}
}
9.2 Prometheus + Grafana监控
Prometheus配置:
yaml
prometheus.yml
global:
scrape_interval: 15s # 抓取间隔
evaluation_interval: 15s # 规则评估间隔
告警规则
rule_files:
- "alert_rules.yml"
抓取配置
scrape_configs:
Spring Boot应用
- job_name: 'spring-boot-apps'
metrics_path: '/actuator/prometheus'
scrape_interval: 10s
static_configs:- targets: ['localhost:8080', 'localhost:8081', 'localhost:8082']
labels:
group: 'microservices'
- targets: ['localhost:8080', 'localhost:8081', 'localhost:8082']
Eureka
- job_name: 'eureka'
static_configs:- targets: ['localhost:8761']
Nacos
- job_name: 'nacos'
static_configs:- targets: ['localhost:8848']
Redis
- job_name: 'redis'
static_configs:- targets: ['localhost:6379']
MySQL
- job_name: 'mysql'
static_configs:- targets: ['localhost:3306']
告警管理器配置
alerting:
alertmanagers:
-
static_configs:
-
targets: ['localhost:9093']
自定义指标:
java
@Component
public class CustomMetrics {
private final MeterRegistry meterRegistry;
// 计数器
private final Counter apiCallCounter;
// 计量器
private final DistributionSummary requestSizeSummary;
// 计时器
private final Timer apiCallTimer;
// 计量器(Gauge)
private final AtomicInteger activeUsers = new AtomicInteger(0);
public CustomMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// 初始化计数器
this.apiCallCounter = Counter.builder("api.calls.total")
.description("API调用总次数")
.tag("application", "user-service")
.register(meterRegistry);
// 初始化计量器
this.requestSizeSummary = DistributionSummary.builder("api.request.size")
.description("API请求大小")
.baseUnit("bytes")
.register(meterRegistry);
// 初始化计时器
this.apiCallTimer = Timer.builder("api.call.duration")
.description("API调用耗时")
.publishPercentiles(0.5, 0.95, 0.99)
.publishPercentileHistogram()
.register(meterRegistry);
// 注册计量器
meterRegistry.gauge("users.active", activeUsers);
// 自定义函数计数器
meterRegistry.more().counter("custom.counter",
Tags.of("type", "business"),
new FunctionCounter() {
private double value = 0;
@Override
public double count() {
return value;
}
public void increment(double amount) {
value += amount;
}
}
);
}
public void recordApiCall(String endpoint, long durationMillis, int requestSize) {
// 递增计数器
apiCallCounter.increment();
// 记录耗时
apiCallTimer.record(durationMillis, TimeUnit.MILLISECONDS);
// 记录请求大小
requestSizeSummary.record(requestSize);
// 使用标签记录特定端点的调用
meterRegistry.counter("api.calls.by.endpoint",
"endpoint", endpoint).increment();
}
public void incrementActiveUsers() {
activeUsers.incrementAndGet();
}
public void decrementActiveUsers() {
activeUsers.decrementAndGet();
}
/**
* 记录业务指标
*/
public void recordBusinessMetric(String businessType, double value) {
meterRegistry.summary("business.metrics",
"type", businessType).record(value);
}
}
// 在控制器中使用
@RestController
public class UserController {
private final CustomMetrics customMetrics;
@PostMapping("/users")
public ResponseEntity<UserDTO> createUser(@RequestBody UserCreateRequest request) {
long startTime = System.currentTimeMillis();
try {
// 执行业务逻辑
UserDTO user = userService.createUser(request);
// 记录指标
long duration = System.currentTimeMillis() - startTime;
int requestSize = request.toString().getBytes().length;
customMetrics.recordApiCall("createUser", duration, requestSize);
return ResponseEntity.ok(user);
} catch (Exception e) {
// 记录错误指标
meterRegistry.counter("api.errors",
"endpoint", "createUser",
"exception", e.getClass().getSimpleName()).increment();
throw e;
}
}
}
十、项目实战与总结
10.1 完整微服务架构项目
项目结构:
text
spring-cloud-microservices/
├── README.md
├── pom.xml # 父工程
├── docker-compose.yml
├── config/ # 配置文件
├── scripts/ # 部署脚本
├── gateway-service/ # API网关
├── user-service/ # 用户服务
├── product-service/ # 商品服务
├── order-service/ # 订单服务
├── payment-service/ # 支付服务
├── notification-service/# 通知服务
├── config-server/ # 配置中心
├── registry-server/ # 注册中心
├── monitor-server/ # 监控服务
└── common/ # 公共模块
├── common-core/ # 核心工具类
├── common-dto/ # 通用DTO
└── common-feign/ # Feign客户端
docker-compose部署:
yaml
version: '3.8'
services:
基础服务
mysql:
image: mysql:8.0
container_name: microservices-mysql
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: microservices
ports:
- "3306:3306"
volumes:
-
mysql_data:/var/lib/mysql
-
./config/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- microservices-network
redis:
image: redis:7-alpine
container_name: microservices-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
networks:
- microservices-network
rabbitmq:
image: rabbitmq:3-management
container_name: microservices-rabbitmq
ports:
-
"5672:5672"
-
"15672:15672"
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: admin123
networks:
- microservices-network
注册中心
nacos:
image: nacos/nacos-server:v2.2.0
container_name: microservices-nacos
environment:
-
MODE=standalone
-
SPRING_DATASOURCE_PLATFORM=mysql
-
MYSQL_SERVICE_HOST=mysql
-
MYSQL_SERVICE_DB_NAME=nacos
-
MYSQL_SERVICE_PORT=3306
-
MYSQL_SERVICE_USER=root
-
MYSQL_SERVICE_PASSWORD=root123
-
NACOS_AUTH_ENABLE=true
ports:
-
"8848:8848"
-
"9848:9848"
-
"9849:9849"
volumes:
- nacos_logs:/home/nacos/logs
depends_on:
- mysql
networks:
- microservices-network
监控服务
prometheus:
image: prom/prometheus:v2.44.0
container_name: microservices-prometheus
ports:
- "9090:9090"
volumes:
-
./config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
-
prometheus_data:/prometheus
command:
-
'--config.file=/etc/prometheus/prometheus.yml'
-
'--storage.tsdb.path=/prometheus'
-
'--web.console.libraries=/etc/prometheus/console_libraries'
-
'--web.console.templates=/etc/prometheus/consoles'
-
'--storage.tsdb.retention.time=200h'
-
'--web.enable-lifecycle'
networks:
- microservices-network
grafana:
image: grafana/grafana:9.5.2
container_name: microservices-grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
volumes:
-
grafana_data:/var/lib/grafana
-
./config/grafana/dashboards:/etc/grafana/provisioning/dashboards
-
./config/grafana/datasources:/etc/grafana/provisioning/datasources
depends_on:
- prometheus
networks:
- microservices-network
zipkin:
image: openzipkin/zipkin:2.24
container_name: microservices-zipkin
ports:
- "9411:9411"
environment:
-
STORAGE_TYPE=mem
-
RABBITMQ_URI=amqp://admin:admin123@rabbitmq:5672
depends_on:
- rabbitmq
networks:
- microservices-network
应用服务
gateway:
build:
context: ./gateway-service
dockerfile: Dockerfile
container_name: microservices-gateway
ports:
- "8080:8080"
environment:
-
SPRING_PROFILES_ACTIVE=docker
-
SPRING_CLOUD_NACOS_SERVER_ADDR=nacos:8848
-
SPRING_CLOUD_NACOS_USERNAME=nacos
-
SPRING_CLOUD_NACOS_PASSWORD=nacos
-
SPRING_REDIS_HOST=redis
-
SPRING_REDIS_PORT=6379
depends_on:
-
nacos
-
redis
networks:
- microservices-network
user-service:
build:
context: ./user-service
dockerfile: Dockerfile
container_name: microservices-user
ports:
- "8081:8081"
environment:
-
SPRING_PROFILES_ACTIVE=docker
-
SPRING_CLOUD_NACOS_SERVER_ADDR=nacos:8848
-
SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/user_db
-
SPRING_DATASOURCE_USERNAME=root
-
SPRING_DATASOURCE_PASSWORD=root123
-
SPRING_REDIS_HOST=redis
-
SPRING_REDIS_PORT=6379
depends_on:
-
nacos
-
mysql
-
redis
networks:
- microservices-network
deploy:
replicas: 2 # 启动2个实例
networks:
microservices-network:
driver: bridge
volumes:
mysql_data:
redis_data:
nacos_logs:
prometheus_data:
grafana_data:
10.2 学习路线总结
Spring Cloud核心组件掌握程度:
text
✅ 服务注册与发现:Nacos/Eureka
✅ 服务调用:OpenFeign/RestTemplate
✅ 服务网关:Spring Cloud Gateway
✅ 服务熔断:Resilience4j
✅ 配置中心:Nacos Config
✅ 链路追踪:Sleuth + Zipkin
✅ 服务监控:Actuator + Prometheus + Grafana
进阶学习方向:
text
下一步学习建议:
-
Spring Cloud进阶
- 分布式事务(Seata)
- 消息驱动(Spring Cloud Stream)
- 安全认证(Spring Cloud Security + OAuth2)
- 任务调度(Spring Cloud Task)
-
容器化与编排
- Docker高级特性
- Kubernetes入门
- Helm包管理
- Service Mesh(Istio)
-
性能优化
- JVM调优
- 数据库优化
- 缓存策略
- 负载测试
-
项目实战
- 电商系统
- 社交平台
- 物联网平台
- 大数据平台
10.3 常见问题解决方案
Q1:服务注册失败
text
可能原因:
- 注册中心未启动
- 网络不通
- 配置错误
- 依赖缺失
解决方案:
- 检查注册中心状态
- 检查网络连接
- 检查application.yml配置
- 检查pom.xml依赖
Q2:服务调用失败
text
可能原因:
- 服务未注册
- 负载均衡问题
- 超时设置不当
- 网络问题
解决方案:
- 检查服务注册状态
- 检查负载均衡配置
- 调整超时时间
- 添加熔断降级
Q3:配置不生效
text
可能原因:
- 配置中心连接失败
- 配置格式错误
- 刷新机制问题
- 配置文件优先级
解决方案:
- 检查配置中心连接
- 检查配置格式
- 手动触发刷新
- 检查配置文件加载顺序
结语:从单体到微服务的成长之路
通过本指南的学习,你已经掌握了Spring Cloud微服务架构的核心技术和实践方法。从单体架构到微服务架构的转变不仅仅是技术架构的变化,更是开发理念和团队协作方式的变革。
关键收获:
✅ 理解了微服务架构的核心概念
✅ 掌握了Spring Cloud全家桶的使用
✅ 学会了服务拆分和服务治理
✅ 掌握了分布式系统的常见问题解决方案
✅ 能够独立搭建完整的微服务系统
记住微服务设计的核心原则:
单一职责:每个服务只做一件事
自治性:服务独立开发、部署、扩展
去中心化:数据、技术栈、团队去中心化
容错设计:考虑失败,设计降级和熔断
自动化:CI/CD、监控、告警自动化
下一步行动建议:
动手实践:按照本指南搭建完整的微服务系统
深入研究:选择1-2个组件深入研究源码
参与开源:贡献代码或文档给Spring Cloud社区
分享知识:写博客、做分享,教是最好的学
资源福利:
关注我并私信"SpringCloud入门",获取:
本文完整代码示例
微服务学习路线图
常见面试题及答案
实战项目模板
下期预告:
《Spring Cloud实战:电商微服务系统从0到1》
商品服务的分布式事务处理
订单服务的状态机设计
支付服务的幂等性保证
库存服务的分布式锁应用
全链路压测与性能优化
今日打卡任务:
👍 点赞本文,支持原创
💾 收藏本文,方便查阅
🚀 动手搭建第一个微服务
👨💻 关注我,获取更多实战教程
在评论区告诉我:
你在学习Spring Cloud中遇到的最大挑战是什么?
你最想了解的微服务主题是什么?
对本文有什么建议或改进意见?
让我们一起在微服务的道路上越走越远! 🚀🌟