SpringCloud总结
Spring Cloud 的概述
Spring Cloud
是一个用于构建分布式系统的开源微服务框架,它基于 Spring Framework 构建,提供了一系列工具和库,用于解决微服务架构中的常见问题。Spring Cloud 旨在简化微服务应用程序的开发、部署和管理,以便开发人员可以更轻松地构建具有高可伸缩性、弹性和高可用性的分布式系统。以下是 Spring Cloud 的概述:
微服务架构支持:Spring Cloud 旨在支持微服务架构,该架构将应用程序拆分为小型、自治的服务,每个服务执行一个特定的功能。这些服务可以独立开发、部署和扩展。
服务注册与发现:Spring Cloud 提供了服务注册与发现机制,允许微服务在启动时向注册中心注册自己,并能够从注册中心查找其他微服务的位置和元数据。Eureka 和 Consul
是常见的服务注册中心。负载均衡:Spring Cloud 支持负载均衡,可以自动将请求分发到多个实例,以提高性能和可用性。Ribbon 是 Spring Cloud 中用于客户端负载均衡的组件。
断路器模式:Spring Cloud 集成了 Hystrix,一种用于实现断路器模式的库,可以在服务故障或超负荷时提供容错机制,防止级联故障。
分布式配置:Spring Cloud Config 允许将应用程序的配置集中管理,以便在不重启服务的情况下进行配置更改。这有助于实现敏捷的配置管理。
网关和路由:Spring Cloud 提供了 Zuul,一个用于构建 API 网关的组件,允许对请求进行路由、过滤和验证。这对于集中式请求处理和安全性非常有用。
消息驱动:Spring Cloud Stream 是一个用于构建消息驱动微服务的框架,支持多种消息中间件(如 Kafka、RabbitMQ 等)。
分布式追踪:Spring Cloud Sleuth 允许跟踪微服务调用链,以帮助排查性能问题和故障。
集成 Spring Boot:Spring Cloud 与 Spring Boot 紧密集成,使微服务的开发变得更加容易。Spring Boot 提供了自动配置和开发工具,而 Spring Cloud
构建在此之上,为微服务应用程序提供了更多的分布式系统功能。开发者友好:Spring Cloud 致力于提供开发者友好的 API 和工具,使微服务开发变得更加简单。
Spring Cloud
是一个强大的微服务框架,它提供了许多有用的功能和组件,使开发人员能够构建可扩展和弹性的分布式系统。它可以与其他 Spring 项目(如 Spring Boot 和 Spring Security)无缝集成,为微服务架构提供了全面的支持。它被广泛应用于云原生应用程序和微服务架构中。
Spring Cloud 基本环境配置
直连方式访问
-
首先创建一个新的Maven模块
SC-01-DemoRestTemplate
,然后创建模块选择Spring lnitializr
创建服务提供者SC-01-Provider
,勾选Web
起步依赖
-
创建控制器
package com.itxw.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassName ProiderController * @Description: TODO * @Author: */ @RestController public class ProviderController { @RequestMapping("/test") public String test(){ return "SpringCloud 的服务提供者"; } }
-
修改
application.properties
服务端口号为8081# 设置服务消费者端口号 server.port=8081
-
创建服务消费者
SC-02-Consumer
,并勾选 Web 起步依赖 -
创建
RestTemplate
对象到Spring
容器中,com/itxw/config/RestTemplateConfig.java
package com.itxw.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * @ClassName RestTemplateConfig * @Description: TODO */ @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
-
创建控制器,并注入
RestTemplate
对象,实现远程调用package com.itxw.controller; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; /** * @ClassName ConsumerController * @Description: TODO * @Author: */ @RestController public class ConsumerController { // 用于远程访问,注入之前需要先定义这个对象到 Spring 容器中,详见 config.RestTemplateConfig @Resource private RestTemplate restTemplate; @RequestMapping("/test") public String test(){ String URL = "http://localhost:8081/test"; // URL : 请求地址 // String.class : 请求响应返回类型 ResponseEntity<String> response = restTemplate.getForEntity(URL, String.class); System.out.println(response.getStatusCode()); System.out.println(response.getStatusCodeValue()); System.out.println(response.getHeaders()); Object body = response.getBody(); return "SpringCloud的服务消费者 ---> " + body; } }
-
修改
application.properties
服务端口号为8082
# 设置服务消费者端口号 server.port=8082
-
测试
localhost:8082/test
使用注册中心访问
SpringCloud 中推荐使用的注册中心是 Eureka,它不需要额外安装任何服务,只需要在项目搭建的时候添加起步依赖即可
注意:此处需要使用特定的版本支持,新版本中很多功能还未能兼容
- SpringBoot 版本采用 2.3.12-RELEASE
- SpringCloud 版本采用 Hoxton.SR11
-
首先创建一个新的Maven模块
SC-02-Eureka
,然后创建模块选择Spring lnitializr
创建SC-02-Eureka-Server
,勾选Web
起步依赖,并添加Eureka Server
起步依赖。
-
修改
application. Properties
配置文件# 指定 tomcat 端口号,就是 EurekaServer 的访问端口 server.port=9100 # 设置该服务注册中心的 hostname eureka.instance.hostname=localhost # 防止自己注册自己 eureka.client.register-with-eureka=false # 不检索其他服务,因为服务注册中心本身则作用就是维护服务实例,不需要去检索其他服务 eureka.client.fetch-registry=false # 指定服务注册中心的位置 eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
-
在引导类上添加注解,激活
Eureka Server
@EnableEurekaServer
-
启动服务进行测试
http://localhost:9100/
-
创建模块选择
Spring lnitializr
创建SC-02-Eureka-Provider
,勾选Web
起步依赖,并添加Eureka Discovery client
起步依赖。(注:创建后记得更改pom.xml文件中的版本号 SpringBoot 版本采用 2.3.12-RELEASE SpringCloud 版本采用 Hoxton.SR11)
-
修改
application. Properties
配置文件# 提供者端口号 server.port=8081 # 服务名称(pom.xml 文件中的模块名) spring.application.name=SC-02-Eureka-Provider # 指定 Eureka 的访问地址 eureka.client.service-url.defaultZone=http://localhost:9100/eureka
-
在引导类中添加注解,激活注册中心客户端
@EnableEurekaClient
-
编写控制器文件
com/itxw/controller/ProviderController.java
package com.itxw.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassName ProviderController * @Description: TODO * @Author: */ @RestController public class ProviderController { @RequestMapping("/test") public String test(){ return "使用 Eureka 注册中心实现的服务提供者"; } }
-
启动服务进行测试
http://localhost:8081/
-
创建模块选择
Spring lnitializr
创建SC-02-Eureka-Consumer
,勾选Web
起步依赖,并添加Eureka Discovery client
起步依赖。
(注:创建后记得更改pom.xml文件中的版本号 SpringBoot 版本采用 2.3.12-RELEASE SpringCloud 版本采用 Hoxton.SR11)
-
配置
application.properties
文件# 提供者端口号 server.Port=8082 # 服务名称(pom.xml 文件中的模块名) spring.application.name=SC-02-Eureka-Consumer # 指定 Eureka 的访问地址 eureka.client.service-url.defaultZone=http://localhost:9100/eureka
-
com/itxw/config/RestTemplateConfig.java
配置文件 和com/itxw/controller/ConsumerController.java
控制器package com.itxw.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; /** * @Classname RestTemplateConfig * @Description: TODO * @Author: User */ @Configuration public class RestTemplateConfig { // 如果通过注册中心访问服务,必须使用 LoadBalanced 注解,添加 Ribbon 负载均衡支持 //使用@LoadBalanced注解,可以实现透明的客户端负载均衡,而不需要手动选择服务实例,这对于构建弹性和可伸缩的微服务应用程序非常有帮助 @LoadBalanced // 标注此注解后,RestTemplate就具有了客户端负载均衡能力 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
package com.itxw.controller; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; /** * @Classname ConsumerController * @Description: TODO * @Author: User */ @RestController public class ConsumerController { // 用于远程访问,注入之前需要先定义这个对象到 Spring 容器中,详见 config.RestTemplateConfig @Resource private RestTemplate restTemplate; @RequestMapping("/test") public String test(){ // url : 请求地址, String.class : 请求响应返回类型 // 根据注册中心的服务名进行访问 String url = "http://SC-02-Eureka-Provider/test"; ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class); System.out.println(forEntity.getBody()); System.out.println(forEntity.getStatusCode()); String info = forEntity.getBody(); return "使用 Eureka 注册中心的服务消费者" + info; } }
-
在引导类中添加
@EnableEurekaClient
注解,激活消费者客户端 -
启动服务进行测试
http://localhost:8082/
搭建高可用 Eureka 注册中心
高可用的 Eureka 注册中心指的是在分布式系统中,采用多个 Eureka 服务器实例进行部署和配置,不同的 Eureka Server 之间相互指向注册,以确保系统在某些服务器实例出现故障或不可用的情况下,仍能保持服务的可用性和稳定性。搭建多台 Eureka Server 的具体步骤和搭建单节点一样,pom.xml 依赖都是相同的,只是在配置文件 application.xml 中需要做修改。
高可用 Eureka 注册中心的特点包括:
- 多实例部署: 在不同的服务器或云服务上部署多个 Eureka 服务器实例,每个实例都能够相互注册,这样就形成了一个高可用的集群。
- 服务注册和发现: 微服务应用通过向 Eureka 服务器注册自身的信息,其他服务则可以通过 Eureka 服务器查询服务的位置信息,从而实现服务之间的通信。
- 负载均衡: 客户端(微服务)通过负载均衡算法连接到多个 Eureka 服务器实例上,从而分摊请求,避免单点故障。
- 自我保护机制: Eureka 注册中心具有自我保护机制,当某个时刻注册中心出现网络分区故障时,该注册中心仍能保持一定时间的服务注册和发现能力,确保系统的稳定性。
- 健康检查: Eureka 客户端定期向注册中心发送心跳,注册中心会剔除那些长时间未发送心跳的服务实例,确保只有健康的服务被其他服务发现。
- 故障转移和恢复: 当某个 Eureka 服务器实例发生故障时,负载均衡器可以将流量转移到其他健康的实例上,从而保持服务的连续性。
-
创建一个空的Maven模块
SC-03-Eureka-Cluster
,然后创建两个注册中心服务模块,选择Spring lnitializr
创建SC-03-Eureka-Server-1001
和SC-03-Eureka-Server-1002
,勾选Web
起步依赖,并添加Eureka Server
起步依赖。 -
配置两个模块的
application.properties
文件,properties
文件添加自我保护SC-03-Eureka-Cluster\SC-03-Eureka-Server-1001\src\main\resources\application.properties
# 指定 tomcat 端口号 server.port=1001 # 设置该服务注册中心的 hostname eureka.instance.hostname=SC-03-Eureka-Server-1001 # 防止自己注册自己 eureka.client.register-with-eureka=false # 不检索其他服务,因为服务注册中心本身则作用就是维护服务实例,不需要去检索其他服务 eureka.client.fetch-registry=false # 指定服务注册中心的位置-在1001的注册服务中指向1002 eureka.client.service-url.defaultZone=http://SC-03-Eureka-Server-1002:1002/eureka # 注册中心服务端添加配置 # 关闭自我保护,默认值是true表示开启,false表示关闭 # 如果某服务在指定的时间内不能提供心跳信息,那么 Eureka 就会将这个服务从注册中心清除 eureka.server.enable-self-preservation=false
SC-03-Eureka-Cluster\SC-03-Eureka-Server-1002\src\main\resources\application.properties
# 指定 tomcat 端口号 server.port=1002 # 设置该服务注册中心的 hostname eureka.instance.hostname=SC-03-Eureka-Server-1002 # 防止自己注册自己 eureka.client.register-with-eureka=false # 不检索其他服务,因为服务注册中心本身则作用就是维护服务实例,不需要去检索其他服务 eureka.client.fetch-registry=false # 指定服务注册中心的位置-在1001的注册服务中指向1002 eureka.client.service-url.defaultZone=http://SC-03-Eureka-Server-1001:1001/eureka # 注册中心服务端添加配置 # 关闭自我保护,默认值是true表示开启,false表示关闭 # 如果某服务在指定的时间内不能提供心跳信息,那么 Eureka 就会将这个服务从注册中心清除 eureka.server.enable-self-preservation=false
-
修改本地 DNS 解析文件
C:\Windows\System32\drivers\etc\hosts
127.0.0.1 SC-03-Eureka-Server-1001 127.0.0.1 SC-03-Eureka-Server-1002
-
在引导类中添加服务器注解并启动测试:可以看到1001注册中心指向1002,1002注册中心指向1001
-
尝试添加服务提供者和消费者配置,
properties
文件添加自我保护# 注册中心客户端添加配置 # 每间隔 2s ,想服务器发送一次心跳,证明自己还活着,默认值为30 eureka.instance.lease-renewal-interval-in-seconds=2 # 告诉客户端,如果当前服务 10s 之内还没有给你发送心跳 # 就表示我已经挂了,将我从注册中心剔除掉,默认值为 90 eureka.instance.lease-expiration-duration-in-seconds=10
-
通过测试可以确定down掉一个服务器,可以采用另外一个服务器继续访问。
客户端Ribbon负载均衡
Ribbon 是 Netflix 公司开源的一个负载均衡器,主要用于在分布式系统中对服务间的调用进行控制。它能够为客户端应用提供控制 HTTP 和 TCP 客户端的行为,以实现负载均衡、故障转移、容错处理等功能。Ribbon 通常与 Eureka、Consul、Zookeeper 等注册中心相结合使用,用于从服务注册中心获取服务端列表,并通过一定的负载均衡策略,将客户端的请求分发到不同的服务端实例上,从而提高系统的可用性和可靠性。
- Nginx 是服务端负载均衡,服务清单列表会存放在 Nginx 服务器中
- Ribbon 是客户端负载均衡,服务清单列表会存放在注册中心中。
Ribbon 负载均衡的特点包括:
- 负载均衡策略:Ribbon 提供了多种负载均衡策略,如轮询、随机、加权轮询、加权随机等,可以根据实际的业务需求和场景选择适合的负载均衡策略。
- 服务健康检查:Ribbon会周期性地检查服务端实例的健康状况,剔除不健康的实例,保证请求只被转发到健康的服务端上,避免请求因为调用不可用的服务而失败。
- 重试机制:Ribbon 具备请求重试功能,当某次请求失败时,可以根据配置的重试策略自动重试,提高请求成功率。
- 性能监控:Ribbon提供了丰富的性能监控功能,可以监控各个服务的请求量、处理时间、成功率等指标,帮助开发人员及时发现系统中的瓶颈和故障点。
- 可扩展性:Ribbon 是一个可扩展的负载均衡器,可以通过定制化的规则来实现更加复杂的负载均衡策略,满足不同场景下的需求。
-
首先创建一个新的Maven模块
SC-04-Ribbon
,然后创建模块选择Spring lnitializr
创建SC-04-Server
,勾选Web
起步依赖,并添加Eureka Server
起步依赖。其中application.properties
配置文件如下:# 指定 tomcat 端口号,就是 EurekaServer 的访问端口 server.port=9100 # 设置该服务注册中心的 hostname eureka.instance.hostname=localhost # 防止自己注册自己 eureka.client.register-with-eureka=false # 不检索其他服务,因为服务注册中心本身则作用就是维护服务实例,不需要去检索其他服务 eureka.client.fetch-registry=false # 指定服务注册中心的位置 eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka ## 关闭自我保护,默认值是true eureka.server.enable-self-preservation=false
-
创建
SC-04-Provider-8081
和SC-04-Provider-8082
两个服务提供者模块。其中application.properties
配置文件如下:server.port=8081 # 注意此处两个模块的名字相同 spring.application.name=SC-04-Provider # 指定 Eureka 的访问地址 eureka.client.service-url.defaultZone=http://localhost:9100/eureka eureka.instance.lease-renewal-interval-in-seconds=2 eureka.instance.lease-expiration-duration-in-seconds=10
server.Port=8082 spring.application.name=SC-04-Provider # 指定 Eureka 的访问地址 eureka.client.service-url.defaultZone=http://localhost:9100/eureka eureka.instance.lease-renewal-interval-in-seconds=2 eureka.instance.lease-expiration-duration-in-seconds=10
-
编写
SC-04-Provider-8081
和SC-04-Provider-8082
模块对应控制器文件com/itxw/controller/ProviderController.java
package com.itxw.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassName ProviderController * @Description: TODO * @Author: */ @RestController public class ProviderController { @RequestMapping("/test") public String test(){ return "Ribbon 注册中心的提供者-8081"; // return "Ribbon 注册中心的提供者-8082"; } }
-
编写
RestTemplate
配置文件package com.itxw.config; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RoundRobinRule; 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; /** * @Classname RestTemplateConfig * @Description: TODO * @Author: User */ @Configuration public class RestTemplateConfig { @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } @Bean public IRule iRule(){ // return new RandomRule(); // 设置为随机策略 return new RoundRobinRule(); // 设置为轮询策略 } }
-
创建
SC-04-Consumer
消费者模块。其中application.properties
配置文件如下:server.port=8080 spring.application.name=SC-04-Consumer eureka.client.service-url.defaultZone=http://localhost:9100/eureka eureka.instance.lease-renewal-interval-in-seconds=2 eureka.instance.lease-expiration-duration-in-seconds=10
-
编写
SC-04-Consumer
对应控制器文件package com.itxw.controller; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; /** * @ClassName ConsumerController * @Description: TODO * @Author: */ @RestController public class ConsumerController { @Resource private RestTemplate restTemplate; @RequestMapping("/test") public String test(){ String URL = "SC-04-Provider/test"; ResponseEntity<String> forEntity = restTemplate.getForEntity(URL, String.class); String info = forEntity.getBody(); return "Eureka 注册中心的服务消费者 ---> " + info; } }
-
进行测试(实现轮询负载均衡)
Hystrix服务熔断
Hystrix 是 Netflix 开源的一个用于处理分布式系统中的服务容错和熔断的库。服务熔断是一种重要的容错机制,用于防止分布式系统中的故障服务对整体系统的影响。
- 1.熔断机制的背景:在分布式系统中,某个服务可能由于各种原因(例如,超时、异常、资源不足等)而导致响应时间延长或服务不可用。如果不采取措施,这可能会导致系统的级联故障,从而影响整个应用程序的可用性。
- 2.Hystrix 服务熔断
Hystrix 提供了服务熔断机制,用于监测和控制对特定服务的请求。当服务的错误率或响应时间超过阈值时,Hystrix 会自动打开断路器,不再将请求发送到该服务。这可以减少对不稳定服务的访问,避免资源浪费,并提高系统的可用性。- 3.熔断状态
Hystrix 引入了三个状态来描述服务的熔断状态:
- 关闭状态(Closed): 初始状态,所有请求都会被执行并进行错误率或响应时间的监控。
- 打开状态(Open): 当错误率或响应时间超过阈值时,断路器会打开,所有请求都将被拒绝,不再发送到服务。
- 半开状态(Half-Open): 在一段时间后,Hystrix 会允许一部分请求通过以检测服务是否已经恢复正常。如果这些请求成功,断路器将继续处于关闭状态,否则,它会回到打开状态。
- 4.配置参数
Hystrix 允许开发人员配置各种参数,如错误率阈值、滑动窗口大小、熔断时间窗口等,以适应特定的应用需求。- 5.熔断的优势
- 防止故障服务对整个系统的影响。
- 提高系统的可用性和稳定性。
- 减少资源浪费,避免不必要的请求。
- 提供实时监控和反馈,有助于问题诊断和修复。
熔断使用并获取异常信息
-
首先创建一个新的Maven模块
SC-05-Ribbon
,然后创建模块选择Spring lnitializr
创建SC-05-Server
,勾选Web
起步依赖,并添加Eureka Server
起步依赖。创建SC-05-Provider
和SC-05-Consumer
,勾选Web
起步依赖,并添加Eureka Client
起步依赖。其中application.properties
配置文件上文。 -
服务消费者
pom.xml
中添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
引导类中添加注解
@EnableCircuitBreaker // 表示激活 hystrix 熔断 // 或者使用 =============================================== @SpringCloudApplication // 该注解表示这是一个 SpringCloud 的项目,所以同时包含以下三个注解 @SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker
-
服务消费者中,相关请求服务测试
package com.itxw.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; /** * @Classname ConsumerController * @Description: TODO * @Author: User */ @RestController public class ConsumerController { @Resource private RestTemplate restTemplate; /** * 通过 throwable 获取异常信息,如果是服务提供者出现了错误(除零错误、空指针异常),获取到的信息是 HttpServerErrorException$InternalServerError,如果是因为超时,获取到的异常信息为 HystrixTimeoutException */ public String error(Throwable throwable){ System.out.println(throwable.getClass()); System.out.println(throwable.getMessage()); System.out.println(throwable.getCause()); return "通过 HystrixCommand 回调函数error()输出"; } /** * String fallbackMethod() default "" * 返回值为字符串 */ // @HystrixCommand(fallbackMethod="error") @HystrixCommand(fallbackMethod="error",commandProperties={@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds", value="10000")}) @RequestMapping("/test") public String test(){ //制造异常 System.out.println(1/0); String url = "http://SC-05-Provider/test"; ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class); String info = forEntity.getBody(); return "消费者 --->" + info; } }
-
服务提供者中制造异常或者延迟
package com.itxw.controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; /** * @Classname ProviderController * @Description: TODO * @Author: User */ @RestController public class ProviderController { @RequestMapping("/test") public String test(){ //制造异常 //System.out.println(1/0); //制造延迟 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return "服务提供者"; } }
-
测试
忽略异常信息
-
// ignoreExceptions 属性为表示为要忽略掉的异常类型,值为 class 类型的数组 // 要忽略掉的异常类型必须为 Throwable 或其 子类类型 // 如果忽略了指定的某个类型的异常之后,如果出现了这个异常,将不触发熔断,直接抛出异常给用户 @HystrixCommand(fallbackMethod = "error", ignoreExceptions = {NullPointerException.class})
自定义异常熔断器
通过自定义异常熔断器解决远程服务提供者的熔断问题,如果对当前消费者进行熔断,需要采用常规熔断@HystrixCommand
方法。
-
创建
SC-05-Hystrix\SC-05-Consumer\src\main\java\com\itxw\hystrix\MyHytrix.java
package com.itxw.hystrix; /** * @Classname MyHytrix * @Description: TODO 自定义熔断器类 * @Author: User */ import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; import org.springframework.web.client.RestTemplate; /** * 自定义熔断器类针对远程服务提供者进行熔断,需要继承熔断器抽象父类,需要对服务提供者的返回值类,也决定当前熔断器进行服务操作时的返回值类型。 * 如果对当前消费者进行熔断,需要采用常规熔断方法 * 这个抽象父类中不包含无参构造方法,所以需要手动添加父类构造方法的调用 */ public class MyHytrix extends HystrixCommand<String>{ private String url; private RestTemplate restTemplate; public MyHytrix(String url, RestTemplate restTemplate) { // 调用 MyHytrix 类的另一个构造函数的方式 // 创建一个 Hystrix 命令设置,将该命令放入一个没有具体名称的默认命令分组。 // 这是 Hystrix 配置的一部分,允许你对不同的命令进行分类和管理,以便在运行时对它们进行监控和应用断路器等容错机制。 this(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(""))); this.url = url; this.restTemplate = restTemplate; } protected MyHytrix(Setter setter) { super(setter); } @Override protected String getFallback() { // return super.getFallback(); return "通过自定义熔断类回调函数getFallback()输出"; } @Override protected String run() throws Exception { // return null; return restTemplate.getForObject(url,String.class); } }
-
此时消费者控制器不再需要常规熔断操作
@RestController public class ConsumerController { @Resource private RestTemplate restTemplate; @RequestMapping("/test") public String test(){ String url = "http://SC-05-Provider/test"; // 实例化自定义熔断类 MyHytrix myHytrix = new MyHytrix(url, restTemplate); // 执行 myHytrix 类的逻辑,返回 String String info = myHytrix.execute(); // ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class); // String info = forEntity.getBody(); return "消费者 --->" + info; } }
-
提供者控制器
@RestController public class ProviderController { @RequestMapping("/test") public String test(){ //制造异常 System.out.println(1/0); return "服务提供者"; } }
-
测试
Feign
Feign的基本使用
Feign是一个声明式、模板化的HTTP客户端库,用于简化编写HTTP请求代码。它是Netflix开发的一部分,特别适用于微服务架构中的服务间通信。以下是Feign的一些重要特点和介绍:
- 1.声明式API:Feign允许你通过编写接口来定义HTTP请求,而不需要编写具体的请求代码。你只需定义一个接口,然后用注解来描述HTTP请求,Feign会自动生成请求代码。
- 2.集成负载均衡:Feign可以集成负载均衡器,比如Netflix的Ribbon,使你能够轻松地进行服务发现和调用多个服务实例。
- 3.内置编码器和解码器:Feign自动处理HTTP请求和响应的编码和解码,你无需手动解析HTTP响应。
- 4.请求和响应日志:Feign支持请求和响应的日志记录,这在调试和监控时非常有用。
- 5.支持多种HTTP方法:Feign支持GET、POST、PUT、DELETE等HTTP方法,以及丰富的HTTP头部设置。
- 6.可扩展性:Feign允许你定义自定义的拦截器和错误处理器,以满足不同的需求。
- 7.集成其他Spring Cloud组件:Feign与Spring Cloud的其他组件(如Eureka、Hystrix等)无缝集成,使构建和管理微服务应用变得更加容易。
- 8.支持异步请求:Feign支持异步HTTP请求,可以提高性能和并发能力。
-
首先创建一个新的Maven模块
SC-06-Feign
,然后创建模块选择Spring lnitializr
创建SC-06-Server
,勾选Web
起步依赖,并添加Eureka Server
起步依赖。创建SC-06-Provider
和SC-06-Consumer
,勾选Web
lombok
起步依赖,并添加Eureka Client
起步依赖。其中application.properties
配置文件上文。 -
SC-06-Consumer
消费者添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
SC-06-Provider
和SC-06-Consumer
创建用户类User
package com.itxw.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @Classname User * @Description: TODO * @Author: User */ @Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private Integer age; }
-
SC-06-Provider\src\main\java\com\itxw\controller\ProviderController.java
服务提供者控制器代码package com.itxw.controller; import com.itxw.domain.User; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; /** * @Classname ProviderController * @Description: TODO * @Author: User */ @RestController public class ProviderController { //简单测试 @RequestMapping("/test") public String test(){ return "服务提供者"; } //返回用户类 @RequestMapping("/test01") public User test01(){ return new User("张三",18); } //返回List集合 @RequestMapping("/test02") public List<User> test02(){ ArrayList<User> userList = new ArrayList<>(); userList.add(new User("李四",15)); userList.add(new User("王五",16)); userList.add(new User("赵六",17)); return userList; } //传递普通参数 @RequestMapping("/test03") public User test03(String name,Integer age){ return new User(name,age); } //用户类参数传递 @RequestMapping("/test04") //服务提供者添加@Requestbody接受请求体数据 public User test04(@RequestBody User user){ return user; } }
-
SC-06-Feign\SC-06-Consumer\src\main\java\com\itxw\service\TestService.java
在服务消费者中添加接口package com.itxw.service; import com.itxw.domain.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * @Classname TestService * @Description: TODO * @Author: User */ @FeignClient(name = "SC-06-Provider") public interface TestService { @RequestMapping("/test") String test(); @RequestMapping("/test01") User test01(); @RequestMapping("/test02") List<User> test02(); @RequestMapping("/test03") //@RequestParam 是用于从请求中提取参数值 name age 的注解 //发送给Provider的请求地址为/test03?name=name&age=age User test03(@RequestParam("name") String name,@RequestParam("age") Integer age); @RequestMapping("/test04") //@Requestbody将当前user作为请求体,服务提供者也必须添加@Requestbody接受请求体数据 User test04(@RequestBody User user); }
-
消费者控制器代码
package com.itxw.controller; import com.itxw.domain.User; import com.itxw.service.TestService; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; /** * @Classname ConsumerController * @Description: TODO * @Author: User */ @RestController public class ConsumerController { @Resource private TestService testService; //简单测试 @RequestMapping("/test") public String test(){ String info = testService.test(); return "服务消费者 ---> " + info; } //返回用户类 @RequestMapping("/test01") public String test01(){ User user = testService.test01(); return "服务消费者 ---> " + user; } //返回List集合 @RequestMapping("/test02") public String test02(){ List<User> userList = testService.test02(); return "服务消费者 ---> " + userList; } //传递普通参数 @RequestMapping("/test03") public String test03(){ User user = testService.test03("张三", 18); return "服务消费者 ---> " + user; } //用户类参数传递 @RequestMapping("/test04") public String test04(){ User user = testService.test04(new User("张三", 18)); return "服务消费者 ---> " + user; } }
-
在消费者引导类中激活
Feign
@EnableFeignClients
-
测试
Feign负载均衡
在Spring Cloud中,Feign与Ribbon集成,以便执行客户端负载均衡。这意味着,当使用Feign来调用远程服务时,它将利用Ribbon来选择目标服务的实例,实现负载均衡。以下是如何使用Feign和Ribbon进行负载均衡的一般步骤:
-
添加依赖:首先,确保项目中包含了Feign、Ribbon和Eureka(如果使用Eureka作为服务注册中心)的依赖。可以在
pom.xml
中添加如下依赖:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
启用Feign客户端:在Spring Boot应用程序的主类上添加
@EnableFeignClients
注解,以启用Feign客户端。@SpringBootApplication @EnableFeignClients public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }
-
创建Feign客户端接口:定义一个接口,用
@FeignClient
注解来指定要调用的远程服务的名称,并编写用于与远程服务通信的方法。示例:@FeignClient(name = "my-service") // 指定要调用的服务名称 public interface MyServiceClient { @GetMapping("/api/resource") String getResource(); }
-
使用Feign客户端:在服务中注入
MyServiceClient
,然后使用它来调用远程服务。@Service public class MyServiceConsumer { private final MyServiceClient myServiceClient; @Autowired public MyServiceConsumer(MyServiceClient myServiceClient) { this.myServiceClient = myServiceClient; } public String consumeResource() { return myServiceClient.getResource(); } }
-
用服务注册与发现(如果使用Eureka):如果使用Eureka作为服务注册中心,确保应用程序配置了Eureka客户端以注册和发现服务。可以在
application.properties
中进行相应的配置。eureka.client.service-url.defaultZon=http://localhost:8761/eureka/
-
负载均衡配置:默认情况下,Feign与Ribbon集成,Ribbon将使用默认的负载均衡策略(通常是轮询)来选择目标服务的实例。如果需要使用不同的负载均衡策略,可以配置Ribbon的策略,如在
application.properties
中配置Ribbon的策略类名:my-service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
Feign的熔断
Feign是一个用于微服务通信的声明式HTTP客户端,它可以与熔断器(Hystrix)集成,以提供熔断功能,从而增加系统的稳定性和可靠性。需要注意的是:Feign通过自定义熔断是远程熔断,若熔断当前控制器采用@HystrixCommand(fallbackMethod = “error”)
熔断是一种防止故障蔓延的机制,当调用远程服务的时候,如果服务不可用或者出现故障,熔断器可以暂时中断对该服务的调用,避免长时间的等待,减少资源占用,同时也可以提供一种降级的方式,以确保系统的核心功能仍然可用。
在Spring Cloud中的Hystrix不再需要使用XML配置文件。在较新的Spring Cloud版本中,Hystrix的配置已经趋向于自动化和注解方式,不再需要显式的XML配置。这是因为Spring Boot和Spring Cloud提供了更便捷的方式来配置Hystrix。
Fallback熔断降级
-
服务消费者
application.properties
# 开启 Feign 对于 Hystrix 服务熔断的支持 true 表示开启,默认 false 表示关闭 feign.hystrix.enabled=true
-
创建消费者客户端接口并定义要调用的远程服务的方法,使用
@FeignClient
注解指定目标服务@FeignClient(name = "SC-06-Provider",fallback = MyHystrix.class) public interface TestService { @RequestMapping("/test") String test(); }
-
自定义熔断类实现该接口
@Component public class MyHystrix implements TestService { @Override public String test() { return "Feign 自定义 fallback方法 熔断被调用"; } }
-
提供者制造异常
@RestController public class ProviderController { //简单测试 @RequestMapping("/test") public String test(){ //制造异常 System.out.println(1 / 0); return "服务提供者"; }
-
测试
FallbackFactory熔断降级
-
同上服务消费者
application.properties
添加如下内容 -
创建消费者客户端接口并定义要调用的远程服务的方法,使用
@FeignClient
注解指定目标服务@FeignClient(name = "SC-06-Provider", fallbackFactory = MyHystrixFactory.class) public interface TestService { @RequestMapping("/test") String test(); }
-
自定义熔断类实现该接口
@Component public class MyHystrixFactory implements FallbackFactory<TestService> { @Override public TestService create(Throwable throwable) { return new TestService() { @Override public String test() { return "Feign 自定义 fallbackFactory 方法 熔断被调用"; } }; } }
-
提供者制造异常
-
测试
Zuul微服务网关
Zuul是Netflix开源的微服务网关,它允许开发人员在微服务架构中构建动态路由、认证、负载均衡、监控等功能。以下是Zuul的一些关键特性和功能:
- 1.动态路由:Zuul可以根据配置将请求动态路由到后端的不同微服务实例。可以根据请求的内容或上下文将其发送到不同的目标服务。
- 2.过滤器:Zuul允许定义一系列的过滤器,这些过滤器在请求进入网关和响应离开网关时执行。过滤器可以用于处理请求、修改请求头、日志记录等一系列操作。
- 3.负载均衡:Zuul可以与服务注册中心(如Eureka)集成,以实现对后端服务的负载均衡。它会自动将请求分发到可用的服务实例上,提高了系统的可用性和可靠性。
- 4.认证和授权:Zuul可以用于认证和授权请求,以确保只有经过授权的用户才能访问特定的资源。它支持多种身份验证和授权机制。
- 5.监控和追踪:Zuul可以与监控和追踪系统集成,提供有关请求和网关性能的详细信息。这有助于监视和调试微服务架构。
- 6.降级和容错:Zuul可以配置降级策略,以处理后端服务不可用的情况。它还可以实现容错机制,提高了系统的弹性。
- 7.动态路由:Zuul支持动态路由,可以在运行时添加、删除或修改路由规则,而无需重启网关。这有助于应对不断变化的服务拓扑。
- 8.请求和响应转换:Zuul可以根据需要修改请求和响应的内容和格式。这对于处理不同客户端的特定需求非常有用。
基本配置
-
首先创建一个空的Maven模块
SC-07-Zuul
,然后创建模块选择Spring lnitializr
创建SC-07-Server
,勾选Web
起步依赖,并添加Eureka Server
起步依赖。创建SC-07-Provider
和SC-07-Consumer
,勾选Web
起步依赖,并添加Eureka Client
起步依赖。其中application.properties
配置文件如上文。 -
创建
SC-07-Provider-ZuulGateway
模块,勾选Web
起步依赖,并添加Eureka Client
起步依赖,在pom.xml
文件中添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
-
修改
application. Properties
配置文件server.port=8083 spring.application.name=SC-07-Provider-ZuulGateway eureka.client.service-url.defaultZone=http://localhost:9100/eureka # 拦截对应服务提供者/api-service/** -- 配置路由规则 要求走网关地址 zuul.routes.SC-07-Provider=/api-zuul/**
-
引导类中激活网关
@EnableZuulProxy
-
SC-07-Provider
对应控制器代码,进行测试@RestController public class ProviderController { @RequestMapping("/test") public String test(){ return "网关开启测试成功~"; } }
-
SC-07-Consumer
对应控制器代码,进行测试@RestController public class ConsumerController { @Resource private TestZuulService testZuulService; @RequestMapping("/test") public String test(){ String info = testZuulService.test(); return "服务消费者 ---> " + info; } }
-
SC-07-Consumer
接口代码package com.itxw.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; /** * @Classname TestZuulTest * @Description: TODO * @Author: User */ // 客户端通过访问网关获取服务提供者信息 @FeignClient(name = "SC-07-Provider-ZuulGateway/api-zuul") public interface TestZuulService { @RequestMapping("/test") String test(); }
-
测试
过滤操作
在Zuul中,过滤器可以用于在请求进入网关和响应离开网关时执行各种操作。可以通过自定义过滤器来实现不同的功能和需求。
Zuul的过滤器分为以下四种类型:
- 1.前置过滤器(Pre Filters):
前置过滤器在请求被路由之前执行。它们用于在请求被路由到后端服务之前进行身份验证、请求修改等操作。如果请求不符合预期,可以在此阶段将其拦截并返回错误响应。- 2.路由过滤器(Route Filters):
路由过滤器用于将请求转发到后端服务。它们可以用于修改请求或请求头以及记录路由信息等操作。通常,负载均衡、请求转发和路由操作在这个阶段进行。- 3.后置过滤器(Post Filters):
后置过滤器在请求被路由到后端服务并得到响应后执行。它们可以修改响应结果、处理响应头、记录日志等操作。后置过滤器常用于对响应进行处理和转换。- 4.错误过滤器(Error Filters):
错误过滤器在整个请求-响应过程中发生错误时执行。它们可以用于处理请求发生异常的情况,记录错误信息、返回适当的错误响应等。错误过滤器能够帮助我们改善系统的容错性和稳定性。.
开发自定义的过滤器需要实现Zuul提供的抽象类
ZuulFilter
,并实现其定义的抽象方法。这些方法包括filterType
(定义过滤器类型),shouldFilter
(定义是否执行过滤器,true 表示启用),run
(定义过滤器的具体逻辑),以及可选的filterOrder
(定义过滤器的执行顺序,数值越小优先级越高)。Zuul还提供了一些内置的过滤器,例如
PreDecorationFilter
(用于设置请求上下文信息)、RoutingFilter
(用于执行路由操作)、SendResponseFilter
(用于发送响应)等。通过合理地使用过滤器,可以在Zuul网关中实现诸如身份验证、请求修改、日志记录、请求转发、响应修改等功能,以满足具体的需求和业务场景。
-
在
SC-07-Provider-ZuulGateway
模块中创建MyZuulFilter
网关过滤类package com.itxw.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * @Classname MyZuulFilter * @Description: TODO * @Author: User */ @Component public class MyZuulFilter extends ZuulFilter { /** * pre前置 routing路由 post后置 error错误 */ @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } // 定义过滤器的具体逻辑 @Override public Object run() throws ZuulException { // 获取了当前请求的上下文对象。在Zuul中,RequestContext 对象包含了当前请求的所有信息,包括请求和响应对象以及其他与请求处理相关的信息。 RequestContext currentContext = RequestContext.getCurrentContext(); // 从上下文对象中获取了当前的HttpServletRequest对象。 // 这个对象代表了客户端发起的HTTP请求,包含了所有的请求信息,比如URL、请求头、参数等 HttpServletRequest request = currentContext.getRequest(); // 尝试从HTTP请求中获取名为"token"的参数的值,"token"通常被用作身份验证或授权的凭证。 String token = request.getParameter("token"); if(null == token || "1234".equals(token)){ // 不满足,中止请求的执行 currentContext.setSendZuulResponse(false); // 设置响应结果的响应头,告诉客户端响应的内容类型 currentContext.addZuulResponseHeader("content-type","text/html;charset=utf-8"); // 设置响应编码,用 999 表示权限不够 currentContext.setResponseStatusCode(999); // 设置响应体内容 currentContext.setResponseBody("需要权限,请获取权限~"); }else { System.out.println("请求成功~"); } return null; } }
-
过滤测试
携带权限码1234进行测试
出现这种情况是因为消费者客户端当前token=1234并没有携带到服务提供者中
可以考虑直接在消费者控制器中添加token=1234使其携带到提供者@FeignClient(name = "SC-07-Provider-ZuulGateway/api-zuul") public interface TestZuulService { @RequestMapping("/test?token=1234") String test(); }
- 当输入
http://localhost:8083/api-zuul/test?token=1234
时,浏览器将正确地将token=1234作为查询参数传递给网关,然后网关将这个查询参数传递给提供者,从而实现了将token=1234带给提供者的目的。这与客户端代码中的硬编码路径不同,客户端代码通常不会自动将查询参数添加到路径中。 - 在微服务架构中,网关通常具有路由和代理的功能,以便将外部请求导向到内部微服务。这导致了一些区别:
1.网关的路由功能: 网关是专门设计用于路由请求的。它有路由配置,可以将不同的请求映射到不同的微服务。这意味着你可以配置网关以根据请求的路径和查询参数将请求路由到不同的微服务,甚至可以修改请求的路径或添加查询参数。这为网关提供了更多的灵活性,允许在网关层面处理请求的转发、修改和路由。
2.浏览器和客户端请求的不同用途: 浏览器请求通常是由用户发起的,用户期望看到网站的内容。浏览器自动将查询参数添加到URL中,以确保用户请求的正确处理。客户端请求通常是通过编程方式发起的,可能需要与多个微服务通信,因此客户端代码负责构建请求的路径、查询参数和其他请求头。这通常涉及到直接指定URL路径和查询参数,而不是通过浏览器自动处理。
- 综上所述,网关具有特殊的路由和代理功能,可以根据请求的不同部分将请求导向不同的微服务。浏览器和客户端请求的区别在于它们的用途和方式。浏览器自动处理查询参数,而客户端代码通常需要手动构建请求的路径和查询参数,除非使用专门设计用于请求微服务的客户端库,这些库可能会提供更高级的路由和参数传递功能。
配置方式
# 配置路由前缀
zuul.prefix=/user
# 忽略请求,不允许访问,拉黑
zuul.ignored-patterns=/api-zuul/test
# 配置自我转发
zuul.routes.gateway.path=/gateway/**
zuul.routes.gateway.url=forward:/itxw
package com.itxw.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Classname GatewayController
* @Description: TODO
* @Author: User
*/
@RestController
public class GatewayController {
@RequestMapping("/itxw")
public String ApiLocal(){
return "网关内自我转发!~";
}
}
异常处理
自定义异常过滤器
-
创建
MyErrorFilter
异常过滤类@Component public class MyErrorFilter extends ZuulFilter { @Override public String filterType() { return "error"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { System.out.println("MyErrorFilter is running ..."); // 获取当前请求的上下文 RequestContext currentContext = RequestContext.getCurrentContext(); // 从上下文中获取响应对象 HttpServletResponse response = currentContext.getResponse(); // 从上下文中获取抛出的异常 ZuulException exception = (ZuulException) currentContext.getThrowable(); response.setHeader("content-type", "text/html;charset=utf-8"); // 设置响应的HTTP状态码为异常中指定的状态码 response.setStatus(exception.nStatusCode); // 打印异常的消息 System.out.println(exception.getMessage()); // 打印异常原因的消息 System.out.println(exception.getCause().getMessage()); try { PrintWriter out = response.getWriter(); out.print("出现异常~"); } catch (IOException e) { e.printStackTrace(); } return null; } }
-
屏蔽系统自带异常处理器
zuul.SendErrorFilter.error.disable=true
-
在其他过滤器中制造一个异常
// 自杀性异常 System.out.print(1 / 0);
-
测试
自定义全局错误页
-
创建
GlobalErrorController.java
控制器@RestController public class GlobalErrorController implements ErrorController { // 返回异常的请求地址 @Override public String getErrorPath() { return "/error"; } @RequestMapping("/error") public String error(){ ZuulException e = (ZuulException) RequestContext.getCurrentContext().getThrowable(); return "全局错误页面信息 ---> " + e.getMessage(); } }
-
关闭自定义异常过滤器并启用默认异常处理器
@Override public boolean shouldFilter() { return false; }
#取消禁用系统默认异常处理器 # 全局错误页面需要默认异常处理器依赖,所以要使用全局页面错误,就不能使用自定义异常类 zuul.SendErrorFilter.error.disable=false