SpringCloud02
1. 负载均衡
- 实际环境中,我们往往会开启很多个provider的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?
- springcloud 2020.0.1 版本之前使用的是Ribbon,springcloud 2020.0.1 版本之后将 Ribbon剔除了,改为使用Loadbalancer替代Ribbon。
- springcloud 2020.0.1 版本之前Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。
- Ribbon是 Netflix 提供的一个基于HTTP和TCP的客户端负载均衡工具。
- Ribbon主要有两个功能:
- 简化远程调用
- 负载均衡
- Loadbalancer和Ribbon的性质是一样的。
1.1. 服务端负载均衡和客户端负载均衡
服务端负载均衡
- 负载均衡算法在服务端。
- 由负载均衡器维护服务地址列表 。
客户端负载均衡
- 负载均衡算法在客户端
- 客户端维护服务地址列表
1.2. 负载均衡方式
- 低版本Eureka中已经集成了Ribbon,所以我们无需引入新的依赖,直接修改代码,默认使用的是轮询的方式。
- springcloud 2020.0.1 版本之后删除了eureka中的ribbon,替代ribbon的是spring cloud自带的LoadBalancer,默认使用的是轮询的方式。
1.3. 开启负载均衡
1.3.1. 启动两个服务提供方式实例
1.3.2. Loadbalancer实现负载均衡
修改springcloud-consumer的配置类,在RestTemplate的配置方法上添加 @LoadBalanced 注解
@Configuration
public class RestTemplateConfiguration {
@Bean
@LoadBalanced //使用resttemplate方法时,进行客户端负载均衡操作
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用
@RestController
public class ConsumerController {
//浏览器 -> consumer,使用resttemplate -> provider
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer")
public String consumer(){
//通过Ribbon或者loadbalancer实现客户端负载均衡,路径必须是提供者在注册
中心中的名称,因为provider做集群操作,
//节点名称在eureka注册中心中是一致的
String url = "http://loadbalancer-provider/provider";
String msg = restTemplate.getForObject(url, String.class);
return msg;
}
}
进行测试
第一次
第二次
1.3.3. 修改Loadbalancer负载均衡策略
LoadBalancer默认提供了两种负载均衡策略:
- RandomLoadBalancer - 随机分配策略
- (默认) RoundRobinLoadBalancer - 轮询分配策略
Ribbon也是一样的。
在springcloud-consumer中
- 编写配置类
@Configuration
public class CustomeLoadBalanceConfiguration {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory){
//设置loadbalancer负载均衡方式为随机方式
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
RandomLoadBalancer randomLoadBalancer = new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
return randomLoadBalancer;
}
}
- 修改配置类
@Configuration
@LoadBalancerClient(value = "loadbalancer-provider",configuration = CustomeLoadBalanceConfiguration.class)
public class RestTemplateConfiguration {
@Bean
@LoadBalanced //使用resttemplate方法时,进行客户端负载均衡操作
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- 测试
重启springcloud-consumer,进行访问,发现会随机访问8082和8084端口的provider
1.3.4. Ribbon实现负载均衡策略(了解)
Ribbon默认的负载均衡策略是简单的轮询,也支持随机分配策略
- 修改pom文件的版本号
springcloud的版本以前使用的是英国伦敦地铁站的名字命名,之后才改成时间作为版本号 名称
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version> <!-- 修改springboot的版本号 -->
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version><!-- 修改springcloud的版本号 -->
</properties>
- Ribbon的其他负载均衡操作和Loadbalancer操作是一样的。
- 修改yml配置文件
server:
port: 8075
spring:
application:
name: eureka-consumer
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
# 设置负载均衡策略,默认为轮询
eureka-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 改为
随机策略
2. 熔断机制Hystrix
- Hystrix,英文意思是豪猪,全身是刺,看起来就不好惹,是一种保护机制。
- Hystrix也是Netflix公司的一款组件。
- Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级 联失败。
2.1. 微服务雪崩问题
- 微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现, 会形成非常复杂的调用链路。
- 如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。 如果此时,某个服务出现异常。
- 微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越 来越多的用户请求到来,越来越多的线程会阻塞。
- 服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。
2.2. Hystrix解决雪崩问题的手段有两个
2.2.1. 隔离
-
线程池隔离(默认)
-
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理。
-
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
-
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。
-
- 信号量隔离
信号量,有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多 个关键代码段不被并发调用。
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空 的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则 必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
隔离只是对雪崩线程进行整理,避免因为一个服务出现问题导致服务雪崩,但是出现问题的服务是没有进行任何处理的。
2.2.2. 降级熔断
- 降级
当访问服务中的一个资源,发生异常或调用超时,即时返回默认数据(错误信息) ,避免 请求线程阻塞。
- 熔断
如果请求访问一个微服务的某个不正常的资源N多次,这个微服务都是在进行降级处理,那说明这个微服务短时间恢复不回来,这个时候就会触发熔断,会对这个微服务的所有的请求 (正常请求,不正常请求)全部禁止访问。等待一段时间,会对正常的请求放开访问,但是对不正常的请求继续进行降级处理。
2.3. 隔离降级
2.3.1. 环境搭建
- springcloudhystrix-provider项目搭建
引入依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.7</spring-cloud.version>
</properties>
<dependencies>
<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.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
application.yml
server:
port: 8082
spring:
application:
name: hystrix-provider
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8081/eureka
controller
@RestController
public class ProviderController {
@RequestMapping("/provider")
public String test(){
return "Hello Hystrix Consumer ";
}
}
引导类
@SpringBootApplication
@EnableEurekaClient
public class SpringcloudDay02HystrixProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudDay02HystrixProviderApplication.class, args);
}
}
- springcloudhystrix-consumer项目搭建
引入依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.7</spring-cloud.version>
</properties>
<dependencies>
<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>
<!--hystrix起步依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!--Feign起步依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
application.yml
server:
port: 8083
spring:
application:
name: hystrix-consumer
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8081/eureka
controller
@RestController
public class ConsumerController {
//浏览器 -> consumer,使用resttemplate -> provider
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; //注册中心发现客户端
@RequestMapping("/consumer")
public String consumer(){
//从注册中心中获取provider的主机地址和端口
List<ServiceInstance> instances =
discoveryClient.getInstances("HYSTRIX-PROVIDER");//参数:客户端在eureka
注册中心的名称
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost(); //获取对应的客户端的主
机地址
int port = serviceInstance.getPort(); //获取对应的客户端的端口
String url = "http://"+host+":"+port+"/provider";
String msg = restTemplate.getForObject(url, String.class);
return msg;
}
}
引导类
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class SpringcloudDay02HystrixConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudDay02HystrixConsumerApplication.class, args);
}
}
运行测试
停止springcloudhystrix-provider,重新测试,会发现,再次访问,需要等待一会
那如果有很多请求访问,都会等待,每次等待请求都会在内存中停留一会,当请求足够多 时,就会导致springcoudhystrix-consumer请求阻塞,从而出现服务雪崩问题。
2.3.2. 降级配置
- springcoudhystrix-consumer中引入相关依赖
<!--hystrix起步依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
- 引导类中开启降级
//低版本可以使用@SpringCloudApplication代替以下所有注解,但高版本已经弃用。
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient //开启discoveryclient支持
@EnableHystrix //springcloud低版本可以使用@EnableCircuitBreaker,但高版本已经
弃用了
public class SpringcloudhystrixConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudhystrixConsumerApplication.class,
args);
}
}
- controller中完成降级逻辑编写
我们改造consumer,当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。 因此需要提前编写好失败时的降级处理逻辑,要使用HystixCommond来完成
@RestController
public class ConsumerController {
//浏览器 -> consumer,使用resttemplate -> provider
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; //注册中心发现客户端
@RequestMapping("/consumer")
@HystrixCommand(fallbackMethod = "failBack") //hystrix熔断隔离降级设置,
fallbackMethod表示降级方法
public String consumer(){
//从注册中心中获取provider的主机地址和端口
List<ServiceInstance> instances =
discoveryClient.getInstances("HYSTRIX-PROVIDER");//参数:客户端在eureka注册
中心的名称
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost(); //获取对应的客户端的主机地址
int port = serviceInstance.getPort(); //获取对应的客户端的端口
String url = "http://"+host+":"+port+"/provider";
String msg = restTemplate.getForObject(url, String.class);
return msg;
}
//返回值类型需要和熔断隔离降级方法返回值类型一致
public String failBack(){
return "请求繁忙,请稍后再试";
}
}
- 正常测试、把提供者服务停掉之后,重新访问,会发现不会在出现原来的请求等待问题,而 是直接返回错误信息。
全局降级配置
@RestController
@DefaultProperties(defaultFallback = "failBack")
public class ConsumerController {
//浏览器 -> consumer,使用resttemplate -> provider
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; //注册中心发现客户端
@RequestMapping("/consumer")
//@HystrixCommand(fallbackMethod = "failBack") //hystrix熔断隔离降级设置,fallbackMethod表示降级方法
@HystrixCommand
public String consumer() {
//从注册中心中获取provider的主机地址和端口
List<ServiceInstance> instances =
discoveryClient.getInstances("HYSTRIX-PROVIDER");//参数:客户端在eureka注册
中心的名称
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost(); //获取对应的客户端的主机地址
int port = serviceInstance.getPort(); //获取对应的客户端的端口
String url = "http://"+host+":"+port+"/provider";
String msg = restTemplate.getForObject(url, String.class);
return msg;
}
//返回值类型需要和熔断隔离降级方法返回值类型一致
public String failBack(){
return "请求繁忙,请稍后再试";
}
}
2.3.3. 设置超时
- 实验中如果请求在超过1秒后都会返回错误信息。
- Hystix的默认超时时长为1秒。
- 修改springcloudhystrix-consumer超时时间。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms
- 改造springcloudhystrix-provider服务提供方的Controller接口,随机休眠一段时间,以触 发熔断。
@RestController
public class HystrixTestController {
@RequestMapping("/consumer")
public String test(){
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello SpringCloud Hystrix";
}
}
- 进行测试,这是会发现,即使springcloudhystrix-provider没有停机,也会触发降级
2.4. 服务熔断
2.4.1. 什么是熔断
2.4.2. 熔断的3个状态
- Closed:关闭状态,所有请求都正常访问。
- Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请 求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次 数最少不低于20次。
- Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随 后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完 全关闭断路器,否则继续保持打开,再次进行休眠计时。
2.4.3. 熔断实现
- 将springcloudhystrix-provider的controller中的睡眠操作去掉,并重启 springcloudhystrix-provider
@RestController
public class ProviderController {
@RequestMapping("/provider")
public String test(){
//耗时操作
/*try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
return "Hello Hystrix Consumer ";
}
}
- 修改springcloudhystrix-consumer的controller,重启springcloudhystrix-consumer
@RestController
@DefaultProperties(defaultFallback = "failBack")
public class ConsumerController {
//浏览器 -> consumer,使用resttemplate -> provider
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; //注册中心发现客户端
@RequestMapping("/consumer/{id}")
//@HystrixCommand(fallbackMethod = "failBack") //hystrix熔断隔离降级设
置,fallbackMethod表示降级方法
@HystrixCommand
public String consumer(@PathVariable("id") Integer id){
//表示当传递的参数是1的时候,请求失败,不是1的时候请求成功
if (id==1){
throw new RuntimeException("访问出错了");
}
//从注册中心中获取provider的主机地址和端口
List<ServiceInstance> instances =
discoveryClient.getInstances("HYSTRIX-PROVIDER");//参数:客户端在eureka注册
中心的名称
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost(); //获取对应的客户端的主机地址
int port = serviceInstance.getPort(); //获取对应的客户端的端口
String url = "http://"+host+":"+port+"/provider";
String msg = restTemplate.getForObject(url, String.class);
return msg;
}
//返回值类型需要和熔断隔离降级方法返回值类型一致
public String failBack(){
return "请求繁忙,请稍后再试";
}
}
- 进行测试
- 为了测试方便,我们可以通过配置修改熔断策略。
hystrix:
command:
default:
# 修改熔断策略
circuitBreaker:
requestVolumeThreshold: 5 #触发熔断的最小请求次数,默认20
sleepWindowInMilliseconds: 10000 #休眠时长,默认是5000毫秒
errorThresholdPercentage: 50 #触发熔断的失败请求最小占比,默认50%
3. Feign
- Feign 是一个声明式的 REST 客户端,它用了基于接口的注解方式,很方便实现客户端配置。(替代restTemplate)
- Feign 最初由 Netflix 公司提供,但不支持SpringMVC注解,后由 SpringCloud 对其封装, 支持了SpringMVC注解,让使用者更易于接受。
- Feign使用代理的方式实现REST客户端操作
- Feign可以替代RestTemplate完成请求操作。
3.1. 使用feign改造consumer工程
1.引入依赖
<!--Feign起步依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
2.开启feign功能
添加@EnableFeignClients 注解
//低版本可以使用@SpringCloudApplication代替以下所有注解,但高版本已经弃用。
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient //开启discoveryclient支持
@EnableHystrix //springcloud低版本可以使用@EnableCircuitBreaker,但高版本已经
弃用了
@EnableFeignClients //开启feign客户端
public class SpringcloudhystrixConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudhystrixConsumerApplication.class,
args);
}
}
3.删除RestTemplate
- feign已经自动集成了Ribbon负载均衡的RestTemplate。所以,此处不需要再注册 RestTemplate。
@Configuration
public class MyConfiguration {
/*@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}*/
}
4.创建Feign客户端
@FeignClient(value = "hystrix-provider") //value属性的值为服务提供方在eureka
注册中心中的注册名称
public interface HystrixFeignClent {
//FeignClient中的方法,必须和服务提供方中要调用的方法保持一致(请求注解、请
求路径、请求参数、方法修饰符、方法返回值、方法名、方法参数要一致)
@RequestMapping("/hystrix")
public String test();
}
5.修改consumer的controller
@RestController
@DefaultProperties(defaultFallback = "testFailBack")//设置整个controller中的
请求降级
public class HystrixTextController {
@Autowired
private HystrixFeignClent hystrixFeignClent;
@RequestMapping("/consumer/{id}")
@HystrixCommand//降级设置
public String test(@PathVariable("id") Integer id){
if (id==1){
throw new RuntimeException("访问出错了");
}
String s = hystrixFeignClent.test();
return s;
}
//返回值类型需要和降级方法返回值类型一致
public String testFailBack(){
return "请求繁忙,请稍后再试";
}
}
6.进行测试
注意:工作中一般使用Feign完成服务消费方访问服务提供方操作。
3.2. Ribbon的支持(了解)
低版本中的Feign中本身已经集成了Ribbon依赖和自动配置。
高版本中已经不再集成Ribbon,需要单独使用。
3.3. Hystrix支持(了解)
3.3.1. 低版本Feign默认也有对Hystrix的集成。
高版本中已经不再集成Hystrix,使用时需要单独引入使用。
3.3.2. 低版本中使用Feign中的Hystrix进行降级操作(了解)
1.先将springcloudhystrix-consumer中的hystrix配置。
2.开启Feign中的hystrix
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
2.编写Feign使用的降级类
@Component
public class FeignFallBack implements HystrixFeignClent{
@Override
public String test() {
return "系统繁忙,请求稍后再试!";
}
}
3.修改Feign注解
@FeignClient(value = "hystrix-provider",fallback = FeignFallBack.class)
//value属性的值为服务提供方在eureka注册中心中的注册名称
public interface HystrixFeignClent {
//FeignClient中的方法,必须和服务提供方中要调用的方法保持一致(请求注解、请求路径、请求参数、方法修饰符、方法返回值、方法名、方法参数要一致)
@RequestMapping("/hystrix")
public String test();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通