12.Hystrix服务降级
分布式系统面临的问题: 复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将会不可避免的出错
服务雪崩
多个微服务之间调用的时候,假设A服务调用B服务和C服务,B服务和C服务又调用其他的微服务,这就是所谓的"扇出"(调用像折扇一样铺开)。
如果扇出的链路上的某个微服务响应时间过长或者不可用,对A服务的调用就会占用越来越多的系统资源,从而引起系统的崩溃,这就是所谓的"雪崩效应"
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列
线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序和系统
Hystrix是用于处理分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖不可避免的会调用失败,比如超时,异常等,
Hystrix能够保障在一个依赖出现问题时,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
"断路器"本身是一个开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似熔断保险丝),想调用方返回一个符合预期的,可以处理的备选响应(FallBack),而不是长时间
的等待或者抛出调用方无法处理的异常,这样保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
Hystrix的功能:
1.服务降级:调用方返回一个符合预期的,可以处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常
2.服务熔断:类比保险丝达到最大访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法返回友好提示
3.服务限流:秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
4.接近实时的监控
服务提供段的服务降级演示:
逻辑:
服务端方法上加上Hystrix服务降级,当服务端的某方法请求超时或者出错时,返回给客户端一个友好提示!
1.pom文件
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
重点1:hystrix包的引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.com.springcloud</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
1.服务端请求方法:
@GetMapping("/producer/threadSleep/{sleepTime}")
重点1:fallbackMethod服务降级方法(当服务访问出错时,或者超时3秒后,会调用标注的方法,@HystrixCommand注解可用用到任何springboot组件中)
@HystrixCommand(fallbackMethod = "fallBackMethod_timeout",
重点2:这里的配置暂时不知道是什么意思?
commandProperties ={
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
}
)
public String getThreadSleep(@PathVariable("sleepTime") int sleepTime){
try {
重点3:线程休眠超过3秒后,导致服务降级,调用服务降级的方法,返回友好返回信息!
Thread.sleep(sleepTime);
} catch (Exception e) {
e.printStackTrace();
}
return "当前线程名:"+Thread.currentThread().getName()+" 端口:"+serverPort;
}
//重点4:getThreadSleep方法的服务降级方法,当出现错误或者超时会调用该方法返回!
public String fallBackMethod_timeout(int sleepTime){
return "系统运行出错了!哭.....休眠时间:"+sleepTime;
}
2.服务端springboot启动类
@SpringBootApplication
@MapperScan(value = "cn.com.springcloud.producer.dao")
@EnableEurekaClient
重点1:使用@EnableCircuitBreaker标签开启Hystrix服务降级
@EnableCircuitBreaker
public class ProducerUserApiApplication8001 {
public static void main(String[] args) {
SpringApplication.run(ProducerUserApiApplication8001.class, args);
}
}
3.测试,这样当客户端访问超时3秒后会返回服务降级方法中的友好返回!
页面输出:系统运行出错了!哭.....休眠时间:8000
客户端的服务降级:
1.在springboot的配置文件application.yml中写法
server:
port: 80
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机版
#defaultZone: http://localhost:7001/eureka
# 集群版
defaultZone: http://eureka7001:7001/eureka,http://eureka7002:7002/eureka
instance:
instance-id: hystrix-userConsumer80
prefer-ip-address: true
spring:
application:
name: hystrix-customer-user
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
重点1:配置下列,开启hystrix
feign:
hystrix:
enabled: true #开启hystrix
2.springboot启动类上加上@EnableHystrix注解,
@SpringBootApplication
@MapperScan(value = "cn.com.springcloud.producer.dao")
@EnableEurekaClient
重点1:加上@EnableHystrix,该注解中包含@EnableCircuitBreaker,开启hystrix的服务降级
@EnableHystrix
public class ProducerUserApiApplication8001 {
public static void main(String[] args) {
SpringApplication.run(ProducerUserApiApplication8001.class, args);
}
}
3.调用服务端的方法
@GetMapping("/consumer/hystrix/getThreadName/{sleepTime}")
重点2:使用@HystrixCommand标签配客户端降级方法!
@HystrixCommand(fallbackMethod = "ThreadSleep_fallBack",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
}
)
public String getThreadSleep(@PathVariable("sleepTime") int sleepTime){
//重点1:调用远程服务的返回线程名称方法
String threadSleep = hystrixService.getThreadSleep(sleepTime);
return threadSleep;
}
结论:
这时服务端和客户端都开启了Hystrix的服务降级,服务端的降级条件是超时3秒,客户端的降级条件是超时1.5秒,当访问休眠1.5秒以上时,根本不会走服务端的降级方法,直接走客户端的服务降级!
配置公共的服务降级方法
@DefaultProperties
1.每个方法都配置一个服务降级方法,技术上是可行的,但是会造成代码的冗余和膨胀!
2.N个除了核心业务逻辑有专属的服务降级方法,其他普通的可以常见一个公共的服务降级方法!
通用的和独享的各自分开,避免代码膨胀,合理减少代码量!
1.第一种:使用@DefaultProperties(defaultFallback = "降级方法")注解
@RestController
重点1:设计了整体的降级方法...
@DefaultProperties(defaultFallback = "threadSleep_fallBackAll")
public class HystrixController {
@GetMapping("/consumer/hystrix/getThreadName/{sleepTime}")
重点2:在需要降级的方法上加上@HystrixCommand注解即可
@HystrixCommand
public String getThreadSleep(@PathVariable("sleepTime") int sleepTime){
String threadSleep = hystrixService.getThreadSleep(3000);
return threadSleep;
}
重点3:整体的降级方法;这里需要注意的是,降级方法的返回是String,所以只能处理返回String类型的方法,如果是其他类型的请求方法上加了HystrixCommand注解
但是返回并不是String会在启动时报错!
public String threadSleep_fallBackAll(){
return "客户端Hystrix----整体----服务降级返回!";
}
}
2.第二种,在feign的接口上加上@FeignClient(value = "PRODUCER-USER-API",fallback = HystrixServiceImp.class)
2.1fegin接口的内容
@Component
重点1:在接口上的FeignClient上加上fallback属性,内容就是该接口的实现类,实现类里包含了各个请求的降级方法
@FeignClient(value = "PRODUCER-USER-API",fallback = HystrixServiceImp.class)
public interface HystrixService {
@GetMapping("/producer/threadSleep/{sleepTime}")
public String getThreadSleep(@PathVariable("sleepTime") int sleepTime);
@GetMapping("/producer/queryUserById/{id}")
public CommResult queryUserById(@PathVariable("id") int id);
}
2.2 接口实现类:
@Component
public class HystrixServiceImp implements HystrixService {
@Override
public String getThreadSleep(int sleepTime) {
return "fegin接口形式的降级方法!sleepTime:"+sleepTime;
}
@Override
public CommResult queryUserById(int id) {
return new CommResult(666,"fegin接口的降级方法!");
}
}
2.3 测试:
当服务端宕机后,或者因服务提供端出现的错误会走接口实现类中的降级方法!
重点总结:
我们平时遇到的问题和异常有:
1.运行时异常
2.超时
3.服务端宕机
等问题!
运行时异常和超时是客户端自己本身出的问题,降级方法必须是前两种的降级方法可以处理,使用fegin接口加实现类的形式处理的是服务端异常!!注意!!