Spring Cloud全面详解 - 从入门到精通
1.1 什么是Spring Cloud
Spring Cloud是构建分布式系统的工具集,它基于Spring Boot,提供了一系列解决分布式系统常见问题的框架。Spring Cloud专注于为典型的分布式系统用例提供良好的开箱即用体验,如配置管理、服务发现、熔断器、智能路由、微代理、控制总线等。
Spring Cloud的核心优势:
- 开箱即用:提供了一系列默认配置,让开发者能够快速上手
- 组件丰富:提供了微服务架构中所需的各种组件和工具
- 基于Spring Boot:与Spring Boot无缝集成,简化了应用开发
- 社区活跃:有活跃的开发社区和丰富的文档资源
- 松耦合设计:各组件之间相对独立,可以根据需要选择使用
1.2 Spring Cloud与微服务架构
微服务架构是一种将应用程序构建为一组小型服务的架构风格。这些服务围绕业务能力构建,可以通过自动化部署机制独立部署,服务之间的通信通常采用轻量级HTTP API。
Spring Cloud与微服务的关系:
- 技术基础:Spring Cloud为实现微服务架构提供了技术基础和框架支持
- 解决方案:Spring Cloud解决了微服务架构中的各种挑战,如服务注册、配置管理等
- 实践指南:Spring Cloud通过其设计模式和最佳实践,指导开发者如何正确实现微服务架构
- 标准化:Spring Cloud为微服务提供了一套相对标准化的实现方案
1.3 Spring Cloud发展历程
Spring Cloud自2014年发布以来,经历了多个版本迭代,主要版本历程如下:
- Angel(2015年):首个正式版本,提供了基础功能
- Brixton(2016年):增强了功能和稳定性
- Camden/Dalston/Edgware(2016-2017年):逐步完善生态系统
- Finchley(2018年):首个基于Spring Boot 2.x的版本
- Greenwich(2019年):增强了Spring Boot 2.x的支持
- Hoxton(2019-2020年):加强云原生支持
- 2020.x/2021.x:采用新的版本命名方式,更加注重云原生支持和响应式编程
- 最新版本:进一步强化云原生和Kubernetes的集成
值得注意的是,Spring Cloud的版本命名从字母顺序(如Angel、Brixton)转变为年份命名(如2020.x),这反映了其发展方向的变化和对云原生环境的更深入支持。
1.4 Spring Cloud技术体系
Spring Cloud的技术体系非常丰富,包括多个子项目,每个子项目解决特定的问题:
- Spring Cloud Netflix:集成Netflix OSS组件(Eureka, Hystrix, Zuul等)
- Spring Cloud Config:提供集中化的配置服务
- Spring Cloud Bus:使用轻量级消息代理连接分布式系统节点
- Spring Cloud Sleuth:提供分布式追踪解决方案
- Spring Cloud Stream:提供消息驱动的微服务框架
- Spring Cloud Task:提供短生命周期的微服务框架
- Spring Cloud Gateway:提供API网关服务
- Spring Cloud Function:促进函数式编程模型
- Spring Cloud OpenFeign:声明式REST客户端
- Spring Cloud Alibaba:集成阿里巴巴微服务组件(Nacos, Sentinel等)
这些组件形成了一个完整的生态系统,使开发者能够根据需要选择适合的工具来构建分布式系统。
2. 微服务架构详解
2.1 微服务架构的优缺点
优点:
- 独立开发和部署:每个服务可以由不同团队独立开发和部署
- 技术异构性:不同服务可以使用不同的技术栈
- 弹性:单个服务故障不会导致整个系统崩溃
- 可扩展性:可以独立扩展需要更多资源的服务
- 易于理解:每个服务的代码量较小,更容易理解和维护
- 组织对齐:服务边界可以与组织结构对齐,形成独立负责的团队
缺点:
- 分布式系统复杂性:需要处理网络延迟、容错、消息序列化等问题
- 服务间通信成本:服务间的远程调用增加了通信成本和延迟
- 数据一致性挑战:分布式事务难以保证数据一致性
- 测试复杂度增加:需要测试每个服务及其交互
- 部署和运维复杂:需要管理多个服务的部署和监控
- 服务依赖管理:需要仔细处理服务间的依赖关系
2.2 微服务设计原则
- 单一职责原则:每个服务应该只负责一个特定的业务功能
- 服务自治原则:服务应该是自包含的,能够独立开发、测试和部署
- 数据隔离原则:每个服务应该管理自己的数据,不直接访问其他服务的数据库
- API契约原则:服务之间通过明确定义的API进行通信
- 容错设计原则:服务应该能够容忍其依赖服务的故障
- 演进式设计原则:服务应该能够独立演进,同时保持向后兼容性
- 监控原则:每个服务都应该提供健康检查和监控指标
2.3 微服务拆分策略
微服务拆分是微服务架构设计中最关键的步骤之一,常见的拆分策略包括:
- 按业务能力拆分:根据业务能力或领域边界划分服务
- 示例:订单服务、商品服务、用户服务
- 优点:与业务对齐,职责清晰
- 按子领域拆分:基于领域驱动设计(DDD)的子领域概念拆分
- 示例:库存管理、物流配送、支付处理
- 优点:更精细的业务划分,更好的领域隔离
- 按资源拆分:围绕特定资源或实体拆分
- 示例:用户资源服务、产品资源服务
- 优点:API边界清晰,资源管理集中
- 按团队拆分:根据组织结构和团队边界拆分
- 示例:移动团队API服务、Web团队API服务
- 优点:团队自主性高,交付效率提升
微服务拆分建议:
- 首先从大的领域边界开始,避免过度拆分
- 关注数据边界,尽量减少跨服务的数据依赖
- 考虑团队结构和沟通成本
- 服务的大小应该能够被一个小团队(5-9人)理解和维护
- 使用"事件风暴"等DDD技术识别领域边界
2.4 领域驱动设计(DDD)与微服务
领域驱动设计(Domain-Driven Design, DDD)是一种软件开发方法,非常适合微服务架构设计。
DDD核心概念:
-
限界上下文(Bounded Context):定义了模型的边界,确保模型在特定上下文中的一致性
(图片来源网络,侵删)- 微服务架构中,每个限界上下文通常对应一个微服务
-
领域模型(Domain Model):反映业务领域核心概念和规则的对象模型
- 微服务中,领域模型是服务内部实现的核心
-
聚合(Aggregate):一组相关对象的集合,作为一个整体对外提供一致性保证
(图片来源网络,侵删)- 微服务中,聚合通常存储在同一个服务中,避免跨服务的数据依赖
-
领域事件(Domain Event):领域中发生的重要事件,可以用于服务间通信
- 微服务中,领域事件常用于实现最终一致性和服务解耦
DDD在微服务架构中的应用:
- 使用限界上下文识别微服务边界
- 基于聚合设计服务内部的数据模型
- 使用领域事件实现服务间的松耦合通信
- 定义上下文映射(Context Mapping)规划服务间的集成方式
示例:电商系统的DDD分析
订单上下文(Order Context): - 聚合:订单(Order)、订单项(OrderItem) - 领域事件:订单创建事件、订单支付事件、订单取消事件 商品上下文(Product Context): - 聚合:商品(Product)、库存(Inventory) - 领域事件:库存变更事件、商品更新事件 用户上下文(User Context): - 聚合:用户(User)、地址(Address) - 领域事件:用户注册事件、用户信息更新事件
3. 服务注册与发现详解
3.1 Eureka详解
Eureka是Netflix开发的服务注册与发现框架,是Spring Cloud中最常用的服务发现组件之一。
3.1.1 Eureka架构
Eureka采用C/S架构,由以下两个组件组成:
- Eureka Server:注册中心服务器,所有服务的注册信息都保存在这里
- Eureka Client:服务提供者和消费者内部的客户端组件,负责与Eureka Server交互
Eureka的基本工作流程:
- 服务提供者启动时,通过Eureka Client向Eureka Server注册自己的信息
- 服务提供者定期向Eureka Server发送心跳,表明服务仍处于可用状态
- 服务消费者从Eureka Server获取服务提供者的信息
- 如果服务提供者不再发送心跳,Eureka Server会将其从注册表中移除
3.1.2 Eureka高可用配置
Eureka支持高可用部署,多个Eureka Server之间相互注册形成集群:
# Eureka Server 1 (node1) server: port: 8761 eureka: instance: hostname: eureka-server1 client: serviceUrl: defaultZone: http://eureka-server2:8762/eureka/,http://eureka-server3:8763/eureka/ # Eureka Server 2 (node2) server: port: 8762 eureka: instance: hostname: eureka-server2 client: serviceUrl: defaultZone: http://eureka-server1:8761/eureka/,http://eureka-server3:8763/eureka/ # Eureka Server 3 (node3) server: port: 8763 eureka: instance: hostname: eureka-server3 client: serviceUrl: defaultZone: http://eureka-server1:8761/eureka/,http://eureka-server2:8762/eureka/
3.1.3 Eureka自我保护机制
Eureka有一个名为"自我保护模式"的特性,当Eureka Server在短时间内丢失过多客户端时(可能是网络分区故障),它会进入自我保护模式,保留所有服务实例信息,而不是将不可用的实例删除。
自我保护模式的配置:
eureka: server: enable-self-preservation: true # 启用自我保护模式(默认) renewal-percent-threshold: 0.85 # 自我保护阈值,默认0.85
3.1.4 Eureka Client详细配置
eureka: client: register-with-eureka: true # 是否注册到Eureka Server fetch-registry: true # 是否从Eureka Server获取注册信息 registry-fetch-interval-seconds: 30 # 获取注册信息的间隔时间 service-url: defaultZone: http://localhost:8761/eureka/ # Eureka Server地址 instance: lease-renewal-interval-in-seconds: 30 # 心跳间隔 lease-expiration-duration-in-seconds: 90 # 服务过期时间 instance-id: ${spring.application.name}:${server.port} # 实例ID prefer-ip-address: true # 使用IP地址而不是主机名 metadata-map: # 元数据,可以存储自定义信息 zone: zone1 version: v1
3.1.5 Eureka服务实例详解
Eureka中的服务实例(Instance)有多种状态:
- UP:服务正常运行
- DOWN:服务已停止,但未从注册表中删除
- STARTING:服务正在启动
- OUT_OF_SERVICE:服务暂时停止服务,但不会被从注册表中删除
- UNKNOWN:状态未知
可以通过配置更改实例状态:
@Autowired private DiscoveryClient discoveryClient; @Autowired private EurekaClient eurekaClient; // 获取所有服务 List services = discoveryClient.getServices(); // 将服务实例设置为OUT_OF_SERVICE状态 eurekaClient.getApplicationInfoManager() .setInstanceStatus(InstanceStatus.OUT_OF_SERVICE);
3.1.6 Eureka完整实战示例
Eureka Server:
org.springframework.cloud spring-cloud-starter-netflix-eureka-server
// EurekaServerApplication.java @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
# application.yml spring: application: name: eureka-server server: port: 8761 eureka: client: register-with-eureka: false fetch-registry: false server: enable-self-preservation: true eviction-interval-timer-in-ms: 60000 # 清理间隔(毫秒)
Eureka Client:
org.springframework.cloud spring-cloud-starter-netflix-eureka-client
// ServiceApplication.java @SpringBootApplication @EnableEurekaClient // 也可以使用@EnableDiscoveryClient public class ServiceApplication { public static void main(String[] args) { SpringApplication.run(ServiceApplication.class, args); } }
# application.yml spring: application: name: sample-service server: port: 8080 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true lease-renewal-interval-in-seconds: 30 lease-expiration-duration-in-seconds: 90
注意: Spring Cloud 2020及以后的版本,@EnableEurekaClient注解已不再必需,只要添加了Eureka Client依赖并配置好服务地址,服务就会自动注册到Eureka Server。
3.2 Nacos服务发现详解
Nacos是阿里巴巴开发的服务发现和配置管理平台,Spring Cloud Alibaba将其集成到Spring Cloud生态中。Nacos不仅提供服务发现功能,还提供配置管理功能,是一个更加综合的解决方案。
3.2.1 Nacos架构
Nacos采用集中式架构,主要包括以下组件:
- Nacos Server:中心服务器,负责服务注册、配置管理
- Nacos Client:客户端组件,与Nacos Server交互
- Nacos Console:Web管理界面,用于服务和配置管理
Nacos的基本工作流程:
- 服务启动时,通过Nacos Client向Nacos Server注册服务
- Nacos Client定期向Nacos Server发送心跳
- 服务消费者从Nacos Server查询服务提供者的信息
- Nacos Server将服务列表推送给服务消费者
3.2.2 Nacos高可用部署
Nacos支持集群部署以保证高可用:
- 独立模式:单实例部署,适用于测试环境
- 集群模式:多实例部署,适用于生产环境
集群模式需要配置cluster.conf文件,列出所有Nacos节点:
# cluster.conf 192.168.0.1:8848 192.168.0.2:8848 192.168.0.3:8848
Nacos集群还需要外部数据库(MySQL)存储数据:
# application.properties spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://localhost:3306/nacos_config db.user=nacos db.password=nacos
3.2.3 Nacos服务发现特性
Nacos的服务发现具有以下特性:
- 健康检查:支持TCP/HTTP/MySQL/自定义健康检查
- 服务保护阈值:类似于Eureka的自我保护机制
- 权重管理:可以为每个实例设置权重,影响负载均衡
- 元数据管理:可以为每个服务设置元数据
- 分组管理:服务可以划分为不同的组
- 命名空间:隔离不同环境或租户的服务
3.2.4 Spring Cloud集成Nacos服务发现
1. 添加依赖:
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery
2. 配置Nacos Server地址:
spring: cloud: nacos: discovery: server-addr: localhost:8848 namespace: public # 命名空间ID group: DEFAULT_GROUP # 分组 cluster-name: DEFAULT # 集群名 register-enabled: true # 是否注册 ephemeral: true # 是否临时实例 weight: 1 # 权重
3. 启用服务发现:
@SpringBootApplication @EnableDiscoveryClient // 开启服务发现客户端 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
3.2.5 Nacos服务发现高级特性
1. 命名空间(Namespace):
命名空间用于隔离服务,通常用于区分不同环境(开发、测试、生产)或不同租户。
spring: cloud: nacos: discovery: namespace: 9e83dc5c-2a8f-4e73-b996-c6633a683888 # 命名空间ID
2. 分组(Group):
分组用于将服务进行逻辑分组,通常根据业务分类。
spring: cloud: nacos: discovery: group: PAY_GROUP # 支付服务组
3. 临时实例与持久实例:
Nacos支持两种类型的实例:
- 临时实例(ephemeral=true):依赖心跳维持,心跳停止后会自动移除
- 持久实例(ephemeral=false):实例信息永久保存,除非手动移除
spring: cloud: nacos: discovery: ephemeral: false # 设置为持久实例
4. 使用权重进行负载均衡:
可以为每个实例设置权重,权重越大,分配到的请求越多。
spring: cloud: nacos: discovery: weight: 2 # 权重为2
5. 使用元数据进行版本控制:
可以使用元数据定义服务版本,并配合负载均衡策略进行版本路由。
spring: cloud: nacos: discovery: metadata: version: v1 env: prod
3.2.6 Nacos服务发现完整示例
1. 启动Nacos Server:
# 下载Nacos wget https://github.com/alibaba/nacos/releases/download/2.0.3/nacos-server-2.0.3.zip # 解压 unzip nacos-server-2.0.3.zip # 启动(单机模式) cd nacos/bin sh startup.sh -m standalone
2. 创建服务提供者:
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery
// ProviderApplication.java @SpringBootApplication @EnableDiscoveryClient public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } } // UserController.java @RestController @RequestMapping("/users") public class UserController { @GetMapping("/{id}") public User getUserById(@PathVariable Long id) { return new User(id, "用户" + id); } }
# application.yml spring: application: name: user-service cloud: nacos: discovery: server-addr: localhost:8848 server: port: 8080
3. 创建服务消费者:
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-loadbalancer
// ConsumerApplication.java @SpringBootApplication @EnableDiscoveryClient public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } } // UserClient.java @Service public class UserClient { @Autowired private RestTemplate restTemplate; public User getUserById(Long id) { return restTemplate.getForObject("http://user-service/users/" + id, User.class); } } // OrderController.java @RestController @RequestMapping("/orders") public class OrderController { @Autowired private UserClient userClient; @GetMapping("/{orderId}/user") public User getUserByOrderId(@PathVariable Long orderId) { // 假设orderId对应的订单所属的用户ID为orderId+1 return userClient.getUserById(orderId + 1); } }
# application.yml spring: application: name: order-service cloud: nacos: discovery: server-addr: localhost:8848 server: port: 8081
3.3 Consul详解
Consul是HashiCorp开发的服务发现和配置管理解决方案,与Eureka、Nacos不同,它是使用Go语言编写的,并提供了一个功能齐全的控制平面,具有服务发现、健康检查、KV存储、多数据中心、安全服务通信等功能。
3.3.1 Consul架构
Consul集群由多个节点组成,节点分为Server和Client两种角色:
- Server:保存和复制数据
- Client:转发请求到Server,无状态
Consul使用Gossip协议管理成员关系、广播消息,并使用Raft协议实现一致性。
3.3.2 Spring Cloud集成Consul
1. 添加依赖:
org.springframework.cloud spring-cloud-starter-consul-discovery
2. 配置Consul:
spring: cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name} instance-id: ${spring.application.name}:${random.value} health-check-path: /actuator/health health-check-interval: 15s
3. 启用服务发现:
@SpringBootApplication @EnableDiscoveryClient public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
3.4 Zookeeper作为注册中心
Zookeeper是Apache软件基金会的一个项目,是一个分布式协调服务,也可以用作服务注册中心。
3.4.1 Spring Cloud集成Zookeeper
1. 添加依赖:
org.springframework.cloud spring-cloud-starter-zookeeper-discovery
2. 配置Zookeeper:
spring: cloud: zookeeper: connect-string: localhost:2181 discovery: register: true root: /services prefer-ip-address: true
3. 启用服务发现:
@SpringBootApplication @EnableDiscoveryClient public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
3.5 各注册中心对比
选择适合的注册中心需要考虑多个因素,以下是主流注册中心的比较:
特性 | Eureka | Nacos | Consul | Zookeeper |
---|---|---|---|---|
开发语言 | Java | Java | Go | Java |
CAP特性 | AP | CP+AP | CP | CP |
健康检查 | 客户端心跳 | TCP/HTTP/MySQL/自定义 | TCP/HTTP/gRPC/自定义 | 客户端心跳 |
负载均衡 | 客户端 | 客户端/服务端 | 客户端/服务端 | 客户端 |
自动注销实例 | 支持 | 支持 | 支持 | 支持 |
访问协议 | HTTP | HTTP/DNS | HTTP/DNS | TCP |
监听支持 | 支持 | 支持 | 支持 | 支持 |
多数据中心 | 不支持 | 支持 | 支持 | 不支持 |
SpringCloud集成 | 原生支持 | 通过Spring Cloud Alibaba | 原生支持 | 原生支持 |
界面 | 一般 | 较好 | 较好 | 无 |
社区活跃度 | 低(已停止开发) | 高 | 高 | 中 |
选择建议:
- 如果使用Spring Cloud全家桶,并希望部署简单,Eureka是不错的选择(虽然已经停止开发)
- 如果需要更多功能(如配置管理、动态DNS、多数据中心),Nacos或Consul是更好的选择
- 如果已经在使用Zookeeper进行协调,可以复用为注册中心
- 对于生产环境,推荐使用Nacos或Consul,它们提供了更多的功能和更好的性能
4. 客户端负载均衡详解
4.1 Ribbon详解
Ribbon是Netflix开发的客户端负载均衡器,可以与Eureka等服务发现组件集成,实现服务消费者的负载均衡。
4.1.1 Ribbon工作原理
Ribbon负载均衡的基本工作流程:
- 服务消费者从服务注册中心获取所有可用的服务提供者列表
- 服务消费者使用负载均衡算法从可用服务列表中选择一个服务实例
- 服务消费者直接与选择的服务实例通信
Ribbon在客户端完成负载均衡,整个过程对服务提供者透明。
4.1.2 Ribbon与RestTemplate集成
通过@LoadBalanced注解,RestTemplate可以集成Ribbon实现负载均衡:
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } // 使用时直接用服务名替代主机名和端口 @Autowired private RestTemplate restTemplate; public User getUser(Long id) { return restTemplate.getForObject("http://user-service/users/" + id, User.class); }
4.1.3 Ribbon负载均衡策略
Ribbon提供了多种负载均衡策略,可以根据需要选择:
- RoundRobinRule:轮询策略,按顺序选择服务器(默认)
- RandomRule:随机策略,随机选择服务器
- RetryRule:重试策略,在一个配置时间段内当选择server不成功,则一直尝试选择一个可用的server
- WeightedResponseTimeRule:加权响应时间策略,根据平均响应时间计算服务实例的权重,响应时间越短权重越大
- BestAvailableRule:最低并发策略,选择并发数最低的服务实例
- AvailabilityFilteringRule:可用过滤策略,过滤掉多次访问故障和并发连接超过阈值的服务实例
- ZoneAvoidanceRule:区域感知策略,使用区域感知逻辑选择服务实例,避开不可用的区域
配置负载均衡策略:
方式一:使用配置类
@Configuration public class RibbonConfig { @Bean public IRule ribbonRule() { return new RandomRule(); // 使用随机策略 } }
方式二:使用配置文件
user-service: # 服务名 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule ConnectTimeout: 1000 ReadTimeout: 3000 MaxAutoRetries: 1 MaxAutoRetriesNextServer: 1 OkToRetryOnAllOperations: true
4.1.4 Ribbon超时与重试机制
Ribbon提供了超时和重试机制,可以通过配置文件设置:
ribbon: ConnectTimeout: 1000 # 连接超时时间(ms) ReadTimeout: 5000 # 读取超时时间(ms) MaxAutoRetries: 1 # 同一实例最大重试次数,不包括首次调用 MaxAutoRetriesNextServer: 1 # 切换实例的最大重试次数 OkToRetryOnAllOperations: false # 是否所有操作都重试
注意:如果启用了Hystrix,还需要确保Hystrix的超时时间大于Ribbon的超时时间,否则Hystrix会提前中断请求。
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 10000
4.1.5 自定义Ribbon负载均衡策略
可以通过实现IRule接口自定义负载均衡策略:
public class CustomRule extends AbstractLoadBalancerRule { @Override public Server choose(Object key) { List servers = getLoadBalancer().getAllServers(); // 实现自定义选择逻辑 // 例如:根据服务器CPU负载、内存使用率等选择 // 或者根据请求的特定参数选择服务实例 if (servers.isEmpty()) { return null; } // 简单实现:优先选择第10个请求的服务器为首选 int serverCount = servers.size(); int index = 0; if (key instanceof Integer) { int requestId = (Integer) key; if (requestId % 10 == 0) { index = requestId % serverCount; } else { index = new Random().nextInt(serverCount); } } else { index = new Random().nextInt(serverCount); } return servers.get(index); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // 初始化配置 } }
注册自定义策略:
@Configuration public class RibbonConfig { @Bean public IRule ribbonRule() { return new CustomRule(); } }
4.1.6 Ribbon请求拦截器
可以通过实现ClientHttpRequestInterceptor接口自定义请求拦截器,添加请求头、日志等功能:
public class RibbonRequestInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 添加请求头 request.getHeaders().add("X-Request-ID", UUID.randomUUID().toString()); // 记录请求日志 System.out.println("发起请求:" + request.getURI()); // 执行请求 long startTime = System.currentTimeMillis(); ClientHttpResponse response = execution.execute(request, body); long endTime = System.currentTimeMillis(); // 记录响应日志 System.out.println("请求耗时:" + (endTime - startTime) + "ms"); return response; } }
注册拦截器:
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors( Collections.singletonList(new RibbonRequestInterceptor()) ); return restTemplate; } }
4.2 LoadBalancer详解
Spring Cloud LoadBalancer是Ribbon的替代品,提供了基于Spring的负载均衡实现。由于Netflix Ribbon已经处于维护模式,Spring Cloud 2020版本后推荐使用Spring Cloud LoadBalancer。
4.2.1 LoadBalancer基本使用
1. 添加依赖:
org.springframework.cloud spring-cloud-starter-loadbalancer
2. 启用LoadBalancer:
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
使用WebClient:
@Bean @LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder(); } // 使用 @Autowired private WebClient.Builder webClientBuilder; public Mono getUser(Long id) { return webClientBuilder.build() .get() .uri("http://user-service/users/" + id) .retrieve() .bodyToMono(User.class); }
4.2.2 LoadBalancer配置
LoadBalancer提供了多种配置选项:
spring: cloud: loadbalancer: ribbon: enabled: false # 禁用Ribbon cache: enabled: true # 启用缓存 ttl: 30s # 缓存时间 retry: enabled: true # 启用重试
4.2.3 LoadBalancer负载均衡策略
LoadBalancer提供了以下负载均衡策略:
- RoundRobinLoadBalancer:轮询策略(默认)
- RandomLoadBalancer:随机策略
- SameInstancePreferenceLoadBalancer:优先选择相同实例
配置负载均衡策略:
@Configuration public class LoadBalancerConfig { @Bean public ServiceInstanceListSupplier serviceInstanceListSupplier() { return new DelegatingServiceInstanceListSupplier( new DiscoveryClientServiceInstanceListSupplier( new DiscoveryClientServiceInstanceListSupplier(null), new RandomLoadBalancer() ) ); } }
或者使用更简洁的方式:
@Configuration @LoadBalancerClient(name = "user-service", configuration = UserServiceConfig.class) public class LoadBalancerConfig { } class UserServiceConfig { @Bean public ReactorLoadBalancer reactorLoadBalancer( Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer( loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name ); } }
4.2.4 自定义LoadBalancer策略
可以通过实现ReactorServiceInstanceLoadBalancer接口自定义负载均衡策略:
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer { private final String serviceId; private final ReactiveLoadBalancer.Factory loadBalancerFactory; private final Random random; public WeightedLoadBalancer(ReactiveLoadBalancer.Factory loadBalancerFactory, String serviceId) { this.serviceId = serviceId; this.loadBalancerFactory = loadBalancerFactory; this.random = new Random(); } @Override public Mono choose(Request request) { ServiceInstanceListSupplier supplier = loadBalancerFactory .getLazyProvider(serviceId, ServiceInstanceListSupplier.class) .getIfAvailable(); return supplier.get().next() .map(instances -> getInstanceResponse(instances, request)); } private Response getInstanceResponse( List instances, Request request) { if (instances.isEmpty()) { return new EmptyResponse(); } // 根据权重选择实例 // 假设每个实例有一个"weight"元数据 int totalWeight = 0; Map weights = new HashMap(); for (ServiceInstance instance : instances) { int weight = 1; // 默认权重 if (instance.getMetadata().containsKey("weight")) { weight = Integer.parseInt(instance.getMetadata().get("weight")); } weights.put(instance, weight); totalWeight += weight; } int randomWeight = random.nextInt(totalWeight) + 1; int current = 0; for (Map.Entry entry : weights.entrySet()) { current += entry.getValue(); if (randomWeight { // 请求前处理 request.getHeaders().add("X-Custom-Header", "value"); }, response -> { // 响应处理 return ResponseEntity.ok(response.getBody()); }, 1 );
5.1.2 RestTemplate配置选项
1. 连接超时和读取超时:
@Bean @LoadBalanced public RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setConnectTimeout(5000); // 连接超时5秒 factory.setReadTimeout(5000); // 读取超时5秒 return new RestTemplate(factory); }
2. 错误处理:
@Bean @LoadBalanced public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { @Override public void handleError(ClientHttpResponse response) throws IOException { if (response.getStatusCode() == HttpStatus.NOT_FOUND) { // 处理404错误 throw new ResourceNotFoundException("资源不存在"); } else { super.handleError(response); } } }); return restTemplate; }
3. 消息转换器:
@Bean @LoadBalanced public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); // 添加自定义的消息转换器 List