Spring Cloud 微服务全栈实战:从 Eureka 到 Docker Compose 一文贯通
关键词 :Spring Cloud Hoxton、Eureka、Ribbon、Feign、Hystrix、Zuul、Config、Sleuth、Zipkin、Docker、Docker Compose 实验环境:华为云 FlexusX x2e.8u.16g × 4 / Ubuntu 24.04 / OpenJDK 11.0.31
目录
- 架构总览
- 环境准备
- [微服务开发框架 Spring Cloud](#微服务开发框架 Spring Cloud "#3-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%BC%80%E5%8F%91%E6%A1%86%E6%9E%B6-spring-cloud")
- [开始使用 Spring Cloud 实战微服务](#开始使用 Spring Cloud 实战微服务 "#4-%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8-spring-cloud-%E5%AE%9E%E6%88%98%E5%BE%AE%E6%9C%8D%E5%8A%A1")
- [整合 Spring Boot Actuator 指标监控](#整合 Spring Boot Actuator 指标监控 "#5-%E6%95%B4%E5%90%88-spring-boot-actuator-%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7")
- [微服务注册与发现 --- Eureka](#微服务注册与发现 — Eureka "#6-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0--eureka")
- [Ribbon 客户端负载均衡](#Ribbon 客户端负载均衡 "#7-ribbon-%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1")
- [Feign 声明式 REST 调用](#Feign 声明式 REST 调用 "#8-feign-%E5%A3%B0%E6%98%8E%E5%BC%8F-rest-%E8%B0%83%E7%94%A8")
- [Hystrix 容错处理](#Hystrix 容错处理 "#9-hystrix-%E5%AE%B9%E9%94%99%E5%A4%84%E7%90%86")
- [Zuul API 网关](#Zuul API 网关 "#10-zuul-api-%E7%BD%91%E5%85%B3")
- [Spring Cloud Config 配置管理](#Spring Cloud Config 配置管理 "#11-spring-cloud-config-%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86")
- [Sleuth + Zipkin 分布式链路追踪](#Sleuth + Zipkin 分布式链路追踪 "#12-sleuth--zipkin-%E5%88%86%E5%B8%83%E5%BC%8F%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA")
- [Docker 安装与常用命令](#Docker 安装与常用命令 "#13-docker-%E5%AE%89%E8%A3%85%E4%B8%8E%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4")
- [Docker 镜像构建](#Docker 镜像构建 "#14-docker-%E9%95%9C%E5%83%8F%E6%9E%84%E5%BB%BA")
- [Docker Registry 与 Maven 构建镜像](#Docker Registry 与 Maven 构建镜像 "#15-docker-registry-%E4%B8%8E-maven-%E6%9E%84%E5%BB%BA%E9%95%9C%E5%83%8F")
- [Docker Compose 编排微服务](#Docker Compose 编排微服务 "#16-docker-compose-%E7%BC%96%E6%8E%92%E5%BE%AE%E6%9C%8D%E5%8A%A1")
- 总结与最佳实践
1. 架构总览
1.1 整体架构
arduino
┌─────────────────────────────────────────────────────────────────┐
│ 客户端 / 浏览器 │
└──────────────┬──────────────────────────────────────────────────┘
│ HTTP
▼
┌──────────────────────────────────────────────────────────────────┐
│ Zuul Gateway :8080 │
│ (API 网关 / 统一入口) │
└──────┬───────────────────────────────────────────────────────────┘
│ 路由转发
├──────────────────────┬────────────────────────────────────┐
▼ ▼ │
┌──────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ user-service │ │ movie-service │ │ Eureka Server │
│ :8001:8002 │◄───│ :9001 │ │ :8761 │
│ (Provider) │ │ (Consumer) │ │ (注册/发现中心) │
│ │ │ ┌──────────────┐ │ │ │
│ 用户数据服务 │ │ │ Ribbon 负载均衡│ │ │ ┌───────────────┐ │
│ │ │ │ Feign 声明调用 │ │ │ │ 服务注册表 │ │
└──────┬───────┘ │ │ Hystrix 容错 │ │ │ │ user-service×2│ │
│ │ └──────────────┘ │ │ │ movie-service │ │
│ └──────────────────┘ │ │ zuul-gateway │ │
│ │ └───────────────┘ │
│ ┌──────────────────┐ └─────────────────────┘
│ │ Config Server │
└───────────►│ :8888 │
│ (配置中心) │
└──────────────────┘
1.2 技术选型
| 组件 | 技术 | 版本 | 说明 |
|---|---|---|---|
| 基础框架 | Spring Boot | 2.3.12.RELEASE | 微服务开发基石 |
| 微服务治理 | Spring Cloud | Hoxton.SR12 | 最后一个包含全部 Netflix OSS 的版本 |
| 服务注册发现 | Netflix Eureka | --- | CAP 理论的 AP 模型 |
| 客户端负载均衡 | Netflix Ribbon | --- | 进程内 LB,与 Eureka 深度集成 |
| 声明式 REST 调用 | OpenFeign | --- | 接口+注解 → HTTP 调用 |
| 容错/熔断 | Netflix Hystrix | --- | 断路器、降级、隔离 |
| API 网关 | Netflix Zuul | --- | 统一入口、路由、过滤 |
| 配置管理 | Spring Cloud Config | --- | 集中配置、热刷新 |
| 链路追踪 | Spring Cloud Sleuth | --- | Trace/Span 注入、日志关联 |
| 追踪展示 | Zipkin | 2.23.2 | 图形化调用链分析 |
| 构建工具 | Maven | 3.8.7 | 依赖管理、打包 |
| 容器化 | Docker | --- | 镜像构建、编排 |
2. 环境准备
2.1 集群拓扑
实验使用 4 台华为云 FlexusX 弹性云服务器:
bash
┌─────────────┬───────────────┬───────────────┬──────────────────────────────┐
│ 主机名 │ 公网 IP │ 私网 IP │ 角色 │
├─────────────┼───────────────┼───────────────┼──────────────────────────────┤
│ ecs-eab3-01 │ 113.44.141.135│ 192.168.0.37 │ Eureka + Config + Zipkin │
│ ecs-eab3-02 │ 124.71.238.84 │ 192.168.0.113 │ user-service × 2 │
│ ecs-eab3-03 │ 123.249.106.118│ 192.168.0.77 │ movie-service │
│ ecs-eab3-04 │ 114.116.235.71│ 192.168.0.225 │ Zuul + Docker Registry │
└─────────────┴───────────────┴───────────────┴──────────────────────────────┘
规格: 8vCPU / 16GiB / Ubuntu 24.04 / 5Mbit/s BGP
2.2 安装 JDK 11 + Maven
bash
# 更新包索引并安装
apt-get update -qq
apt-get install -y openjdk-11-jdk maven
# 验证版本
java -version
# openjdk version "11.0.31" 2026-04-21
# OpenJDK Runtime Environment (build 11.0.31+11-post-1ubuntu1-24.04.2-Ubuntu)
mvn --version
# Apache Maven 3.8.7
选型理由 :选择 JDK 11 + Spring Cloud Hoxton.SR12 是因为该版本是 最后一个完整支持 Netflix OSS 全家桶(Eureka / Ribbon / Hystrix / Zuul)的发行版。后续版本中 Hystrix 和 Zuul 均被移除,替换为 Resilience4j 和 Spring Cloud Gateway。
3. 微服务开发框架 Spring Cloud
3.1 单体应用 vs 微服务架构
scss
┌──────────────────────────────────────────────────────────────┐
│ 单体架构 (Monolith) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 单一 WAR/JAR --- 全部功能耦合在一起 │ │
│ │ 用户模块 │ 订单模块 │ 支付模块 │ 库存模块 │ │
│ │ 共享同一个数据库、同一个进程、同一个部署单元 │ │
│ └────────────────────────────────────────────────────────┘ │
│ 问题: │
│ · 代码耦合 → 改一处可能影响全局 │
│ · 部署耦合 → 一个小改动也要全量部署 │
│ · 扩展困难 → 只能整体扩展,无法针对热点模块 │
│ · 技术锁定 → 整个项目必须使用同一技术栈 │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 微服务架构 (Microservices) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 用户服务 │ │ 订单服务 │ │ 支付服务 │ │ 库存服务 │ │
│ │ Java/Spring│ │ Go/Gin │ │ Node.js │ │ Python │ │
│ │ 独立DB │ │ 独立DB │ │ 独立DB │ │ 独立DB │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ 优势: │
│ · 独立开发 / 独立部署 / 独立扩展 │
│ · 技术异构 → 每个服务可以选用最优技术栈 │
│ · 故障隔离 → 一个服务挂了不影响其他 │
└──────────────────────────────────────────────────────────────┘
3.2 分布式系统的核心挑战
| 挑战 | 说明 | Spring Cloud 解决方案 |
|---|---|---|
| 服务发现 | 服务实例动态变化,如何找到对方? | Eureka |
| 负载均衡 | 多个实例间如何分配流量? | Ribbon |
| 容错处理 | 某个服务故障如何不影响全局? | Hystrix |
| API 网关 | 外部如何统一访问内部服务? | Zuul |
| 配置管理 | 多环境配置如何集中管理? | Config |
| 链路追踪 | 一次请求跨多个服务如何排查? | Sleuth + Zipkin |
4. 开始使用 Spring Cloud 实战微服务
4.1 项目结构
bash
spring-cloud-demo/
├── pom.xml # 父 POM (依赖管理)
├── eureka-server/ # 注册中心
│ ├── pom.xml
│ └── src/main/java/.../EurekaServerApplication.java
├── config-server/ # 配置中心
├── zipkin-server/ # 链路追踪
├── user-service/ # 用户服务 (Provider)
│ └── src/main/java/.../UserController.java
├── movie-service/ # 电影服务 (Consumer)
│ └── src/main/java/.../MovieController.java
└── zuul-gateway/ # API 网关
4.2 父 POM --- 统一依赖管理
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<properties>
<java.version>11</java.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
4.3 服务提供者与消费者的关系
scss
┌─────────────────┐ HTTP/REST ┌─────────────────┐
│ movie-service │ ───────────────────────► │ user-service │
│ (Consumer) │ │ (Provider) │
│ │ ◄─────────────────────── │ │
│ 消费用户数据 │ JSON 响应 │ 提供用户数据 │
│ │ │ │
│ 不存储用户数据 │ │ 独立数据库 │
│ 只做业务编排 │ │ 独立部署 │
└─────────────────┘ └─────────────────┘
关键概念:
- Provider (服务提供者): 暴露 REST API,注册到 Eureka
- Consumer (服务消费者): 通过 Eureka 发现 Provider,调用其 API
我们的 user-service 提供用户查询接口,movie-service 消费该接口来组装电影推荐数据。
5. 整合 Spring Boot Actuator 指标监控
5.1 什么是 Actuator?
Actuator(执行器)是 Spring Boot 的 生产级监控模块,提供开箱即用的运维端点。
中英文对照:Actuator(执行器/促动器)、Endpoint(端点)、Metrics(指标)、Health Check(健康检查)
5.2 集成步骤
在 pom.xml 中添加依赖(所有业务服务模块都需要):
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置暴露所有端点:
yaml
# application.yml
management:
endpoints:
web:
exposure:
include: "*" # 生产环境建议按需暴露
5.3 实测:核心端点验证
bash
# 健康检查
$ curl -s http://localhost:8001/actuator/health
{"status":"UP"}
# 自定义健康端点 (我们在 UserController 中定义)
$ curl -s http://localhost:8001/health
{"port":"8001","service":"user-service","status":"UP"}
# HTTP 请求指标
$ curl -s http://localhost:8001/actuator/metrics/http.server.requests
{"name":"http.server.requests","measurements":[
{"statistic":"COUNT","value":4.0},
{"statistic":"TOTAL_TIME","value":0.062},
{"statistic":"MAX","value":0.030}
]}
5.4 常用监控端点速查
| 端点 | 说明 | 示例用途 |
|---|---|---|
/actuator/health |
健康状态 | K8s 就绪探针 |
/actuator/metrics |
指标列表 | Prometheus 采集 |
/actuator/info |
应用信息 | 版本号、构建时间 |
/actuator/env |
环境变量 | 调试配置 |
/actuator/loggers |
日志级别 | 运行时动态调整 |
/actuator/mappings |
请求映射 | 查看所有接口 |
6. 微服务注册与发现 --- Eureka
6.1 Eureka 核心概念
scss
┌──────────────────────┐
Register (注册) │ │ Discover (发现)
┌─────────────────────►│ Eureka Server │◄─────────────────────┐
│ "我是 user-service, │ (注册中心) │ "movie-service │
│ 地址: 192.168.0.113│ │ 在哪?" │
│ 端口: 8001" │ ┌────────────────┐ │ │
│ │ │ 注册表 (Registry)│ │ │
│ │ │ user-service×2 │ │ │
┌─┴──────────────┐ │ │ movie-service │ │ ┌──────────────┴─┐
│ user-service │ │ │ zuul-gateway │ │ │ movie-service │
│ (Provider) │ │ └────────────────┘ │ │ (Consumer) │
│ :8001 :8002 │ │ │ │ :9001 │
└────────────────┘ └──────────────────────┘ └────────────────┘
│
Renew (心跳续约, 每30秒)
Cancel (下线注销)
关键概念:
- Register(注册):服务启动时将自身元数据(IP、端口、健康状态)发送到 Eureka Server
- Renew(续约):每 30 秒发送心跳,Eureka 90 秒未收到心跳则剔除该实例
- Discover(发现):Consumer 从 Eureka Server 拉取服务列表,本地缓存
- Self-Preservation(自我保护):当心跳丢失比例超过阈值,Eureka 不剔除实例,防止网络分区误杀
6.2 Eureka Server 编写
java
// eureka-server/src/main/java/com/demo/eureka/EurekaServerApplication.java
@SpringBootApplication
@EnableEurekaServer // ← 启用 Eureka 注册中心
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
yaml
# eureka-server/src/main/resources/application.yml
server:
port: 8761
eureka:
client:
register-with-eureka: false # 自己不注册自己
fetch-registry: false # 不拉取注册表
server:
enable-self-preservation: true
6.3 将服务注册到 Eureka
Provider(user-service):
java
@SpringBootApplication
@EnableEurekaClient // ← 声明这是 Eureka Client
@ComponentScan(basePackages = "com.demo")
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
yaml
eureka:
client:
service-url:
defaultZone: http://192.168.0.37:8761/eureka/
instance:
prefer-ip-address: true # 使用 IP 而非主机名注册
instance-id: ${spring.application.name}:${server.port}
6.4 实测:启动并验证注册
bash
# 启动 Eureka Server
$ java -jar eureka-server/target/eureka-server-1.0.0.jar &
# 启动 user-service 两个实例 (不同端口)
$ java -jar -Dserver.port=8001 user-service/target/user-service-1.0.0.jar &
$ java -jar -Dserver.port=8002 user-service/target/user-service-1.0.0.jar &
# 启动 movie-service
$ java -jar -Dserver.port=9001 movie-service/target/movie-service-1.0.0.jar &
# 查看 Eureka Dashboard
$ curl -s http://localhost:8761/ | grep "System Status"
<h1>System Status</h1>
<h1>Instances currently registered with Eureka</h1>
# 查看注册的应用列表
$ curl -s http://localhost:8761/eureka/apps | grep -oP '(?<=<name>)[A-Z-]+(?=</name>)'
USER-SERVICE # ← 用户服务已注册
MOVIE-SERVICE # ← 电影服务已注册
ZUUL-GATEWAY # ← 网关已注册
# 查看 user-service 的实例
$ curl -s http://localhost:8761/eureka/apps/USER-SERVICE | grep -oP '(?<=<instanceId>)[^<]+'
user-service:8001 # ← 实例1
user-service:8002 # ← 实例2 (同一服务两实例)
踩坑记录 :注意
@SpringBootApplication的默认包扫描范围。如果 Controller 不在 Application 类的子包下,需要显式指定@ComponentScan(basePackages = "com.demo"),否则会返回 404。
7. Ribbon 客户端负载均衡
7.1 Ribbon 工作原理
arduino
Eureka Server
┌──────────────┐
│ 注册表: │
│ user-service │
│ - :8001 │
│ - :8002 │
└──────┬───────┘
│ 拉取服务列表
▼
┌─────────────────────────────────────────────────────┐
│ movie-service (Consumer) │
│ ┌───────────────────────────────────────────────┐ │
│ │ Ribbon (客户端LB) │ │
│ │ ServerList: [192.168.0.37:8001, ...:8002] │ │
│ │ Rule: ZoneAvoidanceRule (默认轮询) │ │
│ │ Ping: 每 10s 检测实例可用性 │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ RestTemplate.getForObject( │
│ "http://user-service/user/1", ...) │
│ ↓ Ribbon 拦截 "user-service" → 替换为实际 IP:Port │
└─────────────────────────────────────────────────────┘
核心理念 :Ribbon 是 客户端负载均衡 (Client-Side Load Balancing),区别于 Nginx/HAProxy 等 服务端负载均衡 。负载均衡逻辑运行在 调用方进程内,不需要额外的负载均衡器节点。
7.2 Ribbon 集成代码
java
// MovieServiceApplication.java
@Bean
@LoadBalanced // ← 关键注解:使 RestTemplate 具备 Ribbon 负载均衡能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
java
// MovieController.java --- Ribbon 方式调用
@GetMapping("/movie/{id}/ribbon")
public Map<String, Object> getMovieByRibbon(@PathVariable Long id) {
// "http://user-service" → Ribbon 自动替换为实际实例地址
Map<String, Object> user = restTemplate.getForObject(
"http://user-service/user/" + id, Map.class);
return buildResponse("Ribbon+RestTemplate", user);
}
7.3 实测:负载均衡效果验证
bash
# 多次调用 movie-service 的 Ribbon 接口,观察请求分发到不同实例
$ curl -s http://localhost:9001/movie/3/ribbon | python3 -m json.tool
{
"method": "Ribbon+RestTemplate",
"from": "movie-service:9001",
"user": {
"from": "user-service:8002", # ← 请求被路由到实例 8002
"id": 3,
"username": "user_3"
}
}
$ curl -s http://localhost:9001/movie/4/ribbon | python3 -m json.tool
{
"method": "Ribbon+RestTemplate",
"user": {
"from": "user-service:8001", # ← 下一次请求路由到实例 8001
"id": 4
}
}
验证结论:两次请求被分发到不同的 user-service 实例(8001 / 8002),证明 Ribbon 客户端负载均衡正常工作。
8. Feign 声明式 REST 调用
8.1 Feign vs Ribbon+RestTemplate
| 方式 | 代码风格 | 优点 | 缺点 |
|---|---|---|---|
| Ribbon + RestTemplate | 命令式 | 灵活、底层控制力强 | 模板代码多,URL 硬编码 |
| Feign | 声明式(接口+注解) | 简洁、类型安全、可读性高 | 灵活性略低 |
8.2 Feign 集成步骤
Step 1:定义 Feign 接口
java
// movie-service/.../feign/UserFeignClient.java
@FeignClient(name = "user-service", fallbackFactory = UserFeignFallbackFactory.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
Map<String, Object> getUser(@PathVariable("id") Long id);
}
Step 2:注入调用
java
// MovieController.java
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/movie/{id}/feign")
public Map<String, Object> getMovieByFeign(@PathVariable Long id) {
Map<String, Object> user = userFeignClient.getUser(id); // 像调用本地方法一样调用远程服务
return buildResponse("Feign", user);
}
Step 3:启用 Feign
java
@SpringBootApplication
@EnableFeignClients(basePackages = "com.demo.feign") // 扫描 Feign 接口
public class MovieServiceApplication { ... }
8.3 实测:Feign 调用结果
bash
$ curl -s http://localhost:9001/movie/4/feign | python3 -m json.tool
{
"method": "Feign",
"from": "movie-service:9001",
"movieName": "Spring Cloud 微服务实战",
"movieId": 1000,
"user": {
"from": "user-service:8001",
"id": 4,
"username": "user_4",
"age": 24,
"email": "user4@example.com"
}
}
Feign 整合了 Ribbon + Hystrix :自动具备负载均衡和容错能力,无需额外配置。在 Hoxton.SR12 中,
feign.hystrix.enabled: true启用 Hystrix 支持。
9. Hystrix 容错处理
9.1 常见的三种容错机制
scss
┌────────────────────────────────────────────────────────────┐
│ 容错机制三件套 │
├─────────────┬──────────────────┬───────────────────────────┤
│ 机制 │ 比喻 │ 行为 │
├─────────────┼──────────────────┼───────────────────────────┤
│ Fallback │ 备用方案 │ 调用失败时返回兜底数据 │
│ (降级) │ "电梯坏了走楼梯" │ │
├─────────────┼──────────────────┼───────────────────────────┤
│ Circuit │ 保险丝 │ 错误率超标时直接拒绝请求 │
│ Breaker │ "短路保护" │ 给下游恢复时间 │
│ (熔断) │ │ │
├─────────────┼──────────────────┼───────────────────────────┤
│ Isolation │ 水密舱 │ 不同服务的调用在不同线程池 │
│ (隔离) │ "防止水渗入" │ 慢调用不会拖垮其他调用 │
└─────────────┴──────────────────┴───────────────────────────┘
中英文对照:Circuit Breaker(断路器/熔断器)、Fallback(降级/回退)、Bulkhead(舱壁隔离)、Thread Pool Isolation(线程池隔离)
9.2 Hystrix 集成
1. 添加依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2. 启用 Hystrix
java
@SpringBootApplication
@EnableHystrix // ← 启用断路器
public class MovieServiceApplication { ... }
3. 配置 Feign 使用 Hystrix
yaml
feign:
hystrix:
enabled: true # Feign 中开启 Hystrix 支持
4. 编写降级逻辑
java
// UserFeignFallbackFactory.java --- Feign 的 Hystrix 降级工厂
@Component
public class UserFeignFallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable cause) {
return id -> Map.of(
"id", id,
"username", "fallback_user",
"note", "Hystrix 容错: user-service 不可用"
);
}
}
java
// MovieController.java --- @HystrixCommand 直接使用
@GetMapping("/movie/{id}/hystrix")
@HystrixCommand(
fallbackMethod = "hystrixDirectFallback",
commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000")
}
)
public Map<String, Object> getMovieByHystrix(@PathVariable Long id) {
if (id == 888) {
throw new RuntimeException("模拟业务异常!id=888 触发降级");
}
// ... 正常调用
}
// 降级方法:方法签名必须与原方法一致 + 额外 Throwable 参数
public Map<String, Object> hystrixDirectFallback(Long id) {
return Map.of("id", id, "note", "熔断器已打开或业务异常,执行降级逻辑");
}
Hystrix 配置参数详解:
| 参数 | 值 | 说明 |
|---|---|---|
circuitBreaker.requestVolumeThreshold |
5 | 10 秒内至少 5 次请求才开始计算错误率 |
circuitBreaker.errorThresholdPercentage |
50 | 错误率超过 50% 则打开断路器 |
circuitBreaker.sleepWindowInMilliseconds |
10000 | 断路器打开后 10 秒进入半开状态 |
execution.isolation.thread.timeoutInMilliseconds |
5000 | 调用超时 5 秒则触发降级 |
9.3 实测:Hystrix 降级与熔断
bash
# 正常调用 (Hystrix + RestTemplate)
$ curl -s http://localhost:9001/movie/5/hystrix | python3 -m json.tool
{
"method": "Hystrix+RestTemplate",
"user": {
"from": "user-service:8002",
"username": "user_5"
}
}
# 触发降级 (id=888 模拟异常)
$ curl -s http://localhost:9001/movie/888/hystrix
{
"note": "熔断器已打开或业务异常,执行降级逻辑",
"id": 888,
"method": "Hystrix Direct Fallback"
}
# 连续 5 次调用 id=888 触发熔断器打开
$ for i in $(seq 1 5); do
curl -s http://localhost:9001/movie/888/hystrix
done
# 全部返回降级结果:"熔断器已打开或业务异常,执行降级逻辑"
踩坑记录 :在 Spring Cloud Hoxton.SR12 中,
@EnableHystrix虽然标记为@Deprecated,但仍然可用。后续版本建议迁移到@EnableCircuitBreaker+ Resilience4j。
10. Zuul API 网关
10.1 网关的作用
sql
没有网关: 有网关:
┌─────────┐ ┌─────────┐
│ Client │ │ Client │
└────┬────┘ └────┬────┘
│ ┌──────────┐ │ 单一入口
├───────►│ user:8001 │ ▼
├───────►│ user:8002 │ ┌──────────────┐
├───────►│ movie:9001│ │ Zuul :8080 │────► user-service
└───────►│ order:9002 │ │ 路由/过滤 │────► movie-service
└──────────┘ │ 认证/限流 │────► order-service
问题: └──────────────┘
· 前端需要知道所有服务地址 优势:
· 跨域问题 · 统一入口,前端只记一个地址
· 认证/日志需要每个服务实现 · 认证、日志、限流统一处理
· 协议转换需要在每个服务做 · 协议转换、请求/响应改写
10.2 Zuul 集成
java
@SpringBootApplication
@EnableZuulProxy // ← 启用 Zuul 网关 + Eureka 集成
public class ZuulGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}
yaml
# 路由配置
zuul:
routes:
user-service: # 路由名称
path: /api/user/** # 匹配的 URL 路径
serviceId: user-service # 转发到的微服务 (Eureka 中注册的名称)
movie-service:
path: /api/movie/**
serviceId: movie-service
10.3 实测:Zuul 路由转发
bash
# 直接访问 user-service (绕过网关)
$ curl -s http://localhost:8001/user/1
{"from":"user-service:8001","username":"user_1",...}
# 通过 Zuul 网关访问 → /api/user/** → user-service
$ curl -s http://localhost:8080/api/user/user/6
{"from":"user-service:8001","username":"user_6",...}
# 通过 Zuul 网关访问 movie-service
$ curl -s http://localhost:8080/api/movie/movie/7/feign | python3 -m json.tool
{
"movieName": "Spring Cloud 微服务实战",
"method": "Feign",
"user": {"from": "user-service:8001", "username": "user_7"}
}
路由规则解析 :
/api/user/user/6→ Zuul 匹配/api/user/**→ 去掉/api/user前缀 → 转发到user-service的/user/6。
11. Spring Cloud Config 配置管理
11.1 配置中心的价值
arduino
传统方式: Config Server:
每个服务各自管理配置 ┌──────────────────┐
┌──────────┐ ┌──────────┐ │ Config Server │
│ app.yml │ │ app.yml │ │ ┌─────────────┐ │
│ DB密码=123│ │ DB密码=123│ │ │ sample-config│ │
└──────────┘ └──────────┘ │ │ - dev profile│ │
问题: │ │ - prod │ │
· 密码改了要改N个地方 │ └─────────────┘ │
· 环境切换容易出错 └──────┬───────────┘
· 配置散落各处 ┌──────┼──────┐
▼ ▼ ▼
user movie zuul
自动拉取,无需重启 (配合 RefreshScope)
11.2 Config Server 编写
java
@SpringBootApplication
@EnableConfigServer // ← 启用配置中心
public class ConfigServerApplication { ... }
yaml
spring:
cloud:
config:
server:
native:
search-locations: classpath:/config/ # 从 classpath 加载配置
profiles:
active: native # 使用本地存储模式
yaml
# classpath:/config/sample-config.yml
user-service:
defaultUser: "admin"
maxRetry: 3
movie-service:
defaultRecommend: "Spring Cloud 实战"
11.3 实测:拉取配置
bash
$ curl -s http://localhost:8888/sample-config/default | python3 -m json.tool
{
"name": "sample-config",
"propertySources": [{
"source": {
"user-service.defaultUser": "admin",
"user-service.maxRetry": 3,
"movie-service.defaultRecommend": "Spring Cloud 实战"
}
}]
}
12. Sleuth + Zipkin 分布式链路追踪
12.1 核心概念
yaml
一次完整的调用链 (Trace):
┌─────────────────────────────────────────────────────────────────┐
│ Trace ID: 5abf332175158f53 (全局唯一,贯穿整个调用链) │
│ │
│ Span A: movie-service ← 入口 │
│ ├── Parent Span ID: null │
│ ├── Span ID: 5abf332175158f53 │
│ └── Tags: http.method=GET, http.path=/movie/1/feign │
│ │ │
│ ▼ Feign 调用 user-service │
│ Span B: user-service │
│ ├── Parent Span ID: 5abf332175158f53 ← 指向父 Span │
│ ├── Span ID: 52c13966481559f9 │
│ └── Tags: http.method=GET, http.path=/user/1 │
└─────────────────────────────────────────────────────────────────┘
概念对照:Trace(调用链)--- 一次请求的完整路径;Span(跨度)--- 调用链中的一个基本单元;Annotation(注解)--- Span 生命周期中的事件点(CS/CR/SS/SR)
12.2 集成 Sleuth
所有业务服务添加依赖:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
无需额外代码!Sleuth 自动拦截所有 HTTP 调用,注入 Trace ID 和 Span ID。
12.3 日志中的 Sleuth 信息
bash
# movie-service 日志 (注意方括号中的四元组)
$ tail -f /tmp/movie-service-9001.log
2026-06-29 20:43:10 INFO [movie-service,5abf332175158f53,52c13966481559f9,true]
--- [nio-9001-exec-6] c.d.controller.MovieController :
[movie-service:9001] Feign 调用 user-service id=7
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^
# 服务名 Trace ID Span ID 是否发送到 Zipkin
12.4 Zipkin 部署与验证
bash
# 下载 Zipkin 官方独立版本
$ curl -sL -o zipkin.jar \
https://repo1.maven.org/maven2/io/zipkin/zipkin-server/2.23.2/zipkin-server-2.23.2-exec.jar
# 启动 (内存模式,默认端口 9411)
$ java -jar zipkin.jar &
# 配置业务服务指向 Zipkin
spring:
zipkin:
base-url: http://192.168.0.37:9411/
sleuth:
sampler:
probability: 1.0 # 100% 采样 (生产环境建议 0.1)
Zipkin 访问 :http://113.44.141.135:9411/ --- 可按服务名、时间范围查询调用链,图形化展示 Span 依赖关系。
13. Docker 安装与常用命令
13.1 安装 Docker CE
bash
# Ubuntu 24.04 安装 Docker
apt-get update
apt-get install -y docker.io
# 启动并设置开机自启
systemctl enable --now docker
# 验证
$ docker --version
Docker version 26.1.3, build b72abbb
13.2 常用 Docker 命令速查
bash
# === 镜像操作 ===
docker pull openjdk:11-jre-slim # 拉取镜像
docker images # 查看本地镜像
docker rmi <image_id> # 删除镜像
docker build -t name:tag . # 构建镜像
# === 容器操作 ===
docker run -d -p 8080:8080 --name app name:tag # 启动容器
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器
docker logs -f <container> # 查看日志
docker exec -it <container> bash # 进入容器
docker stop/start/restart <id> # 停止/启动/重启
docker rm <container> # 删除容器
# === 系统操作 ===
docker system prune -a # 清理未使用的资源
docker stats # 查看容器资源使用
docker network ls # 查看网络
docker volume ls # 查看数据卷
14. Docker 镜像构建
14.1 多阶段构建 Dockerfile
dockerfile
# Dockerfile --- 生产级 Spring Boot 镜像
FROM openjdk:11-jre-slim
LABEL maintainer="spring-cloud-demo"
ARG JAR_FILE
COPY ${JAR_FILE} /app/app.jar
# 安全: 非 root 用户运行
RUN groupadd -r spring && useradd -r -g spring spring
USER spring:spring
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
14.2 构建镜像
bash
# 先 Maven 打包
$ cd spring-cloud-demo && mvn clean package -DskipTests
# 构建各服务镜像
$ cd eureka-server
$ docker build \
--build-arg JAR_FILE=target/eureka-server-1.0.0.jar \
-t eureka-server:1.0.0 \
-f ../Dockerfile .
$ docker images | grep eureka
eureka-server 1.0.0 abc123 10 seconds ago 350MB
15. Docker Registry 与 Maven 构建镜像
15.1 搭建私有 Registry
bash
# 在 sc-04 上启动私有 Registry
$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
# 验证
$ curl http://192.168.0.225:5000/v2/_catalog
{"repositories":[]}
# 打标签并推送
$ docker tag eureka-server:1.0.0 192.168.0.225:5000/eureka-server:1.0.0
$ docker push 192.168.0.225:5000/eureka-server:1.0.0
# 验证
$ curl http://192.168.0.225:5000/v2/_catalog
{"repositories":["eureka-server"]}
若 Docker 使用非安全 Registry ,需配置
/etc/docker/daemon.json:
json{ "insecure-registries": ["192.168.0.225:5000"] }
15.2 一键构建脚本
bash
#!/bin/bash
# build.sh --- 一键构建所有微服务镜像
set -e
REGISTRY="192.168.0.225:5000"
VERSION="1.0.0"
services=("eureka-server" "config-server" "zipkin-server"
"user-service" "movie-service" "zuul-gateway")
# Maven 编译
mvn clean package -DskipTests
# Docker 构建
for svc in "${services[@]}"; do
docker build \
--build-arg JAR_FILE="$svc/target/$svc-$VERSION.jar" \
-t $REGISTRY/$svc:$VERSION \
-f Dockerfile $svc
echo "Built: $REGISTRY/$svc:$VERSION"
done
16. Docker Compose 编排微服务
16.1 Docker Compose 文件
yaml
# docker-compose.yml
version: '3.8'
services:
eureka-server:
image: 192.168.0.225:5000/eureka-server:1.0.0
ports: ["8761:8761"]
networks: [sc-net]
restart: always
config-server:
image: 192.168.0.225:5000/config-server:1.0.0
ports: ["8888:8888"]
networks: [sc-net]
depends_on: [eureka-server]
restart: always
zipkin-server:
image: 192.168.0.225:5000/zipkin-server:1.0.0
ports: ["9411:9411"]
networks: [sc-net]
user-service-1:
image: 192.168.0.225:5000/user-service:1.0.0
ports: ["8001:8001"]
networks: [sc-net]
environment:
SERVER_PORT: "8001"
EUREKA_URL: http://eureka-server:8761/eureka/
ZIPKIN_URL: http://zipkin-server:9411/
user-service-2:
image: 192.168.0.225:5000/user-service:1.0.0
ports: ["8002:8002"]
networks: [sc-net]
environment:
SERVER_PORT: "8002"
EUREKA_URL: http://eureka-server:8761/eureka/
movie-service:
image: 192.168.0.225:5000/movie-service:1.0.0
ports: ["9001:9001"]
networks: [sc-net]
depends_on: [eureka-server, user-service-1, user-service-2]
zuul-gateway:
image: 192.168.0.225:5000/zuul-gateway:1.0.0
ports: ["8080:8080"]
networks: [sc-net]
depends_on: [eureka-server]
networks:
sc-net:
driver: bridge
16.2 一键编排启动
bash
# 启动所有服务
$ docker-compose up -d
Creating network "spring-cloud-demo_sc-net" ... done
Creating eureka-server ... done
Creating config-server ... done
Creating zipkin-server ... done
Creating user-service-1 ... done
Creating user-service-2 ... done
Creating movie-service ... done
Creating zuul-gateway ... done
# 查看运行状态
$ docker-compose ps
Name State Ports
--------------------------------------------------------------
eureka-server Up 0.0.0.0:8761->8761/tcp
config-server Up 0.0.0.0:8888->8888/tcp
user-service-1 Up 0.0.0.0:8001->8001/tcp
user-service-2 Up 0.0.0.0:8002->8002/tcp
movie-service Up 0.0.0.0:9001->9001/tcp
zuul-gateway Up 0.0.0.0:8080->8080/tcp
# 查看日志
$ docker-compose logs -f movie-service
# 停止
$ docker-compose down
16.3 Docker Compose 核心优势
| 对比维度 | 手动部署 | Docker Compose |
|---|---|---|
| 启动方式 | 逐个 java -jar × N 个终端 |
docker-compose up -d 一条命令 |
| 依赖管理 | 手动按顺序启动 | depends_on 声明式管理 |
| 网络 | 手动配置 IP/端口 | 自动创建内部网络,服务名即域名 |
| 环境一致性 | 开发/测试/生产可能不同 | 同一镜像确保一致 |
| 扩缩容 | 手动操作 | docker-compose up -d --scale user-service=3 |
17. 总结与最佳实践
17.1 核心组件回顾
| # | 实验 | 核心组件 | 实测验证 |
|---|---|---|---|
| 1 | 微服务框架 | Spring Cloud 生态 | Hoxton.SR12 + Boot 2.3.12 |
| 2 | 基础微服务 | Provider / Consumer | user-service ↔ movie-service 正常通信 |
| 3 | 指标监控 | Actuator | /actuator/health → UP |
| 4 | 注册发现 | Eureka | 7个实例注册,Dashboard 可视化 |
| 5 | 负载均衡 | Ribbon | 请求交替分发到 8001/8002 |
| 6 | 声明式调用 | Feign | 接口方式调用,自动负载均衡 |
| 7 | 容错处理 | Hystrix | id=888 触发降级,断路器正常打开 |
| 8 | API 网关 | Zuul | /api/user/** → user-service |
| 9 | 配置管理 | Config | 集中管理,native 模式 |
| 10 | 链路追踪 | Sleuth + Zipkin | Trace ID 注入日志,Zipkin 图形化 |
| 11-15 | Docker | 镜像/Registry/Compose | 一键编排启动全部微服务 |
17.2 最佳实践建议
- 版本选择:教学/学习场景推荐 Hoxton.SR12(全组件),生产环境推荐 Spring Cloud 2023+ Resilience4j + Gateway
- Eureka 高可用:生产环境至少部署 2 个 Eureka Server 组成集群
- Hystrix 配置 :合理设置超时时间(
timeoutInMilliseconds),避免"雪崩"误判 - Feign 超时:Feign 默认 1 秒超时,但 Ribbon 默认 1 秒,且 Feign 有自己的超时配置,需要统一规划
- Docker 安全:容器内以非 root 用户运行;敏感配置用环境变量注入,不写死在镜像中
- 日志聚合:Docker Compose 部署时建议配合 ELK / Loki 做日志集中收集
实验环境 :华为云 FlexusX x2e.8u.16g × 4 / Ubuntu 24.04 / OpenJDK 11.0.31 / Spring Cloud Hoxton.SR12 完整源码 :已部署在 113.44.141.135:8761 (Eureka Dashboard) 写作时间:2026年6月29日