Spring Cloud入门篇:微服务架构从0到1(20000字完整指南)

一、微服务时代:为什么需要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 # 统一依赖管理

// 单体架构的问题:

  1. 代码库庞大,维护困难
  2. 技术栈单一,无法按需选型
  3. 发布风险高,牵一发而动全身
  4. 扩展性差,只能整体扩展
  5. 可靠性低,一个模块崩溃影响整个系统
    微服务架构的优势:

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. 技术栈灵活,各服务可用不同技术
  3. 容错性强,单点故障不影响整体
  4. 扩展性好,可按需扩展热点服务
  5. 持续交付,快速迭代
    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 服务调用最佳实践

  1. 统一的请求/响应拦截器

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());
}

}

  1. 统一的响应处理

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();
}

}

  1. 调用链跟踪

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

网关的核心功能:

  1. 统一入口:所有外部请求都通过网关
  2. 路由转发:根据规则转发到具体服务
  3. 负载均衡:在多个服务实例间分配请求
  4. 安全认证:统一认证和授权
  5. 限流熔断:保护后端服务
  6. 日志监控:统一收集请求日志
  7. 跨域处理:统一处理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

  1. 配置动态刷新:实时推送配置变更
  2. 多环境支持:namespace + group + dataId
  3. 配置版本管理:历史版本和回滚
  4. 配置监听:监听配置变化
  5. 配置权限:细粒度的权限控制
  6. 配置导入导出:批量操作
    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'

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

下一步学习建议:

  1. Spring Cloud进阶

    • 分布式事务(Seata)
    • 消息驱动(Spring Cloud Stream)
    • 安全认证(Spring Cloud Security + OAuth2)
    • 任务调度(Spring Cloud Task)
  2. 容器化与编排

    • Docker高级特性
    • Kubernetes入门
    • Helm包管理
    • Service Mesh(Istio)
  3. 性能优化

    • JVM调优
    • 数据库优化
    • 缓存策略
    • 负载测试
  4. 项目实战

    • 电商系统
    • 社交平台
    • 物联网平台
    • 大数据平台
      10.3 常见问题解决方案
      Q1:服务注册失败

text

可能原因:

  1. 注册中心未启动
  2. 网络不通
  3. 配置错误
  4. 依赖缺失

解决方案:

  1. 检查注册中心状态
  2. 检查网络连接
  3. 检查application.yml配置
  4. 检查pom.xml依赖
    Q2:服务调用失败

text

可能原因:

  1. 服务未注册
  2. 负载均衡问题
  3. 超时设置不当
  4. 网络问题

解决方案:

  1. 检查服务注册状态
  2. 检查负载均衡配置
  3. 调整超时时间
  4. 添加熔断降级
    Q3:配置不生效

text

可能原因:

  1. 配置中心连接失败
  2. 配置格式错误
  3. 刷新机制问题
  4. 配置文件优先级

解决方案:

  1. 检查配置中心连接
  2. 检查配置格式
  3. 手动触发刷新
  4. 检查配置文件加载顺序
    结语:从单体到微服务的成长之路
    通过本指南的学习,你已经掌握了Spring Cloud微服务架构的核心技术和实践方法。从单体架构到微服务架构的转变不仅仅是技术架构的变化,更是开发理念和团队协作方式的变革。

关键收获:

✅ 理解了微服务架构的核心概念

✅ 掌握了Spring Cloud全家桶的使用

✅ 学会了服务拆分和服务治理

✅ 掌握了分布式系统的常见问题解决方案

✅ 能够独立搭建完整的微服务系统

记住微服务设计的核心原则:

单一职责:每个服务只做一件事

自治性:服务独立开发、部署、扩展

去中心化:数据、技术栈、团队去中心化

容错设计:考虑失败,设计降级和熔断

自动化:CI/CD、监控、告警自动化

下一步行动建议:

动手实践:按照本指南搭建完整的微服务系统

深入研究:选择1-2个组件深入研究源码

参与开源:贡献代码或文档给Spring Cloud社区

分享知识:写博客、做分享,教是最好的学

资源福利:

关注我并私信"SpringCloud入门",获取:

本文完整代码示例

微服务学习路线图

常见面试题及答案

实战项目模板

下期预告:

《Spring Cloud实战:电商微服务系统从0到1》

商品服务的分布式事务处理

订单服务的状态机设计

支付服务的幂等性保证

库存服务的分布式锁应用

全链路压测与性能优化

今日打卡任务:

👍 点赞本文,支持原创

💾 收藏本文,方便查阅

🚀 动手搭建第一个微服务

👨‍💻 关注我,获取更多实战教程

在评论区告诉我:

你在学习Spring Cloud中遇到的最大挑战是什么?

你最想了解的微服务主题是什么?

对本文有什么建议或改进意见?

让我们一起在微服务的道路上越走越远! 🚀🌟

相关推荐
深入技术了解原理5 小时前
引入eureka依赖但是无法注册:无法解析配置属性 ‘eureka.client.service-url.defaultZone‘
spring boot·spring cloud·云原生·eureka
Coder_Boy_5 小时前
基于SpringAI的在线考试系统-试卷管理与考试管理模块联合回归测试文档
人工智能·spring boot·架构·领域驱动
数巨小码人5 小时前
核心架构深度解析-揭开国产数据库内核的神秘面纱
数据库·架构
a努力。5 小时前
蚂蚁Java面试被问:流批一体架构的实现和状态管理
java·后端·websocket·spring·面试·职场和发展·架构
空空kkk6 小时前
Java项目从单体到微服务的演变
java·运维·微服务
川西胖墩墩6 小时前
网站开发完整流程梳理
大数据·数据库·架构·流程图·敏捷流程
专注API从业者6 小时前
淘宝商品 API 接口架构解析:从请求到详情数据返回的完整链路
java·大数据·开发语言·数据库·架构
VekiSon6 小时前
ARM架构——时钟系统与定时器详解
linux·c语言·arm开发·嵌入式硬件·架构
国科安芯7 小时前
抗辐照MCU在核电站交换机中的可靠性验证方法研究
单片机·嵌入式硬件·架构·安全性测试