9. Hystrix服务降级
Hystrix关键特性:服务降级、服务熔断、服务限流
1. Hystrix服务降级
服务降级
:也就是假设对方系统不可用了,向调用方返回一个符合预期的,可备选的响应。比如我们常见的“服务器忙,请稍后重试!”、“系统开小差,请稍后再试!”、“您的内容飞到了外太空…”等,超时不再等待,出错有兜底方案。不用客户等待并立刻返回一个友好的提示,这就是服务降级。
出现服务降级的情况
: ①程序运行异常; ②超时; ③服务熔断触发服务降级; ④线程池/信号量打满也会导致服务降级。
解决方案
: 对方服务超时了或宕机,调用者不能一直卡死等待,必须有服务降级; 对方服务可能OK,调用者自己出故障或有自我要求(自己的等待时间小于服务提供者),自己服务降级。
2. 服务端降级
在springcloud项目中,技术实现上来说Hystrix服务降级放在服务端、客户端均可以,具体根据自己的业务场景判定,但是一般Hystrix服务降级fallback是放在客户端,这里我们均演示一下。
项目结构:
父工程的版本环境:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2.1 引入Hystrix依赖
首先服务端(也就是我们的payment工程)引入hystrix依赖(具体版本根据自己环境决定):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.18</version>
</dependency>
2.2 案例演示
Controller类代码:
@RestController
@RequestMapping("/payment")
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@GetMapping(value = "/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoOk(id);
log.info("========result:{}========", result);
return result;
}
@GetMapping(value = "/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoTimeOut(id);
log.info("========result:{}========", result);
return result;
}
}
服务端业务类简单编写两个方法:一个正常查询方法,一个模拟复查业务耗时的方法:
@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, Payment> implements PaymentService {
@Override
public String paymentInfoOk(Integer id) {
return "线程池:" + Thread.currentThread().getName() + "成功访问paymentInfoOK,id=" + id;
}
@Override
public String paymentInfoTimeOut(Integer id) {
int time = 5;
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + "耗时" + time + "秒成功访问paymentInfoTimeOut,id=" + id;
}
}
简单测试
:两个接口都正常访问和返回: http://localhost:8001/payment/hystrix/ok/1 直接返回 http://localhost:8001/payment/hystrix/timeout/1 等待5s返回(转圈圈一下)
并发请求
:接下来通过jmeter压测接口:http://localhost:8001/payment/hystrix/timeout/1,模拟2w个请求访问我们的paymentInfoTimeOut耗时业务接口。
现象
:发现两个接口都会转圈甚至卡死,我们发现正常直接返回结果的接口也会被同一个微服务里耗时接口拖垮。所以想要系统高可用必须降级处理。假设payment服务端业务逻辑实际处理需要5s,微服务对自身有要求,超过3s作超时处理,也是需要降级的。
原因
:SpringBoot默认集成的是tomcat,tomcat的默认工作线程(10个)被打满了,没有多余的线程来分解压力和处理。
2.3 服务端如何降级
2.3.1 启动类添加激活注解:
@EnableCircuitBreaker
有的环境会提示@EnableCircuitBreaker is deprecated已弃用,用@EnableHystrix注解代替即可。
本demo环境使用的是@EnableHystrix激活注解
2.3.2 业务类方法上添加启用:@HystrixCommand(fallbackMethod = “fallback”)
在fallbackMethod中添加一个方法,value设置为超时时间,当超过超时时间时,就会调用备用的方法
注:降级(FallBack)方法必须与其对应的业务方法在同一个类中,否则无法生效。
@RestController
@RequestMapping("/payment")
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@GetMapping(value = "/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoOk(id);
log.info("========result:{}========", result);
return result;
}
//设置超时时间3s
@HystrixCommand(fallbackMethod = "fallback",commandProperties = {
@HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@GetMapping(value = "/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoTimeOut(id);
log.info("========result:{}========", result);
return result;
}
public String fallback(Integer id){
return "系统繁忙,请稍后再试!线程池:" + Thread.currentThread().getName() +"访问paymentInfoTimeOut,id=" + id;
}
}
测试降级效果:
不会再等待5s,到达设置的超时时间3s就会调用降级方法。
Tip:如果不设置超时时间,
Hystrix默认超时时间是1秒
,我们可以通过hystrix源码看到:找到 hystrix-core.jar包下的HystrixCommandProperties类中的default_executionTimeoutInMilliseconds属性局势默认的超时时间:1000
3. 客户端降级
我们将服务端payment工程刚做的操作恢复到没有任何降级处理的状态,开始演示客户端降级。
3.1 引入Hystrix依赖
客户端工程order同样的操作引入Hystrix依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.18</version>
</dependency>
3.2 启动类添加激活注解@EnableHystrix
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableHystrix
public class CloudOrder {
public static void main(String[] args) {
SpringApplication.run(CloudOrder.class, args);
}
}
3.3 改yml
我们的客户端是通过feign调用服务端,所以在order工程中修改yml,添加内容:
feign:
hystrix:
enabled: true
3.4 业务类方法上添加启用:@HystrixCommand(fallbackMethod = “fallback”)
添加:@HystrixCommand(fallbackMethod = “fallback”,commandProperties = {@HystrixProperty(name =“execution.isolation.thread.timeoutInMilliseconds”,value = “1500”) })及fallback方法:
@RestController
@RequestMapping("/order")
@Slf4j
@DefaultProperties(defaultFallback="globalFallback")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping(value = "/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoOk(id);
log.info("========result:{}========", result);
return result;
}
@HystrixCommand(fallbackMethod = "fallback",commandProperties = {
@HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
@GetMapping(value = "/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoTimeOut(id);
log.info("========result:{}========", result);
return result;
}
public String fallback(Integer id){
return "太久了我不想等待了,我已经等待了1.5s了!线程池:" + Thread.currentThread().getName() +"访问paymentInfoTimeOut,id=" + id;
}
}
Feign接口:
@Component
@FeignClient(value = "CLOUD-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping(value = "/payment/hystrix/ok/{id}")
String paymentInfoOk(@PathVariable("id") Integer id);
@GetMapping(value = "/payment/hystrix/timeout/{id}")
String paymentInfoTimeOut(@PathVariable("id") Integer id);
}
客户端测试调用服务端超时降级:
我们发现方法一对一降级,那样代码将越来越多,越来越重复,代码膨胀,所以要设置全局通用服务降级方法。
4. 全局通用服务降级
避免代码膨胀,我们设置全局通用服务降级方法,如果需要特殊配置的在另外单独配。
小结:如果注解@HystrixCommand定义fallback方法,走自己定义的fallback方法,反之走全局默认的@DefaultProperties中指定的方法。 总之:
全局降级方法的优先级较低
,只有业务方法没有指定其降级方法时,服务降级时才会触发全局回退方法。若业务方法指定它自己的回退方法,那么在服务降级时,就只会直接触发它自己的回退方法,而非全局回退方法。
1、类上加注解:DefaultProperties(defaultFallback=” “)指定全局兜底方法
2、方法加注解:@HystrixCommand
3、编写全局降级方法
@RestController
@RequestMapping("/order")
@Slf4j
@DefaultProperties(defaultFallback="globalFallback")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping(value = "/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoOk(id);
log.info("========result:{}========", result);
return result;
}
@HystrixCommand
@GetMapping(value = "/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoTimeOut(id);
log.info("========result:{}========", result);
return result;
}
//全局方法降级
public String globalFallback(){
return "全局降级!线程池:" + Thread.currentThread().getName() +"访问paymentInfoTimeOut";
}
}
测试客户端全局降级:
继续思考,所有的降级都在Controller层处理是不是
耦合度很高
,服务降级每个方法都添加hyxtrix兜底的方法,造成方法的膨胀,既然是服务间的调用,我们能不能将和服务端工程相关的feign接口整体降级,也就是和payment工程相关的都统一降级。
5. 解耦服务降级
只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦,也就是@FeignClient注解修饰的这个调用类。
5.1 新建一个解耦降级处理类
新建一个解耦降级处理类PaymentFallbackService.java,该类实现@FeignClient修饰的PaymentHystrixService接口:
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfoOk(Integer id) {
return "全局解耦降级处理PaymentFallback->paymentInfoOk!";
}
@Override
public String paymentInfoTimeOut(Integer id) {
return "全局解耦降级处理PaymentFallback->paymentInfoTimeOut!";
}
}
5.2 Feign客户端定义的接口添加fallback
Feign客户端定义的接口PaymentHystrixService.java中添加fallback = PaymentFallbackService.class
@Component
@FeignClient(value = "CLOUD-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping(value = "/payment/hystrix/ok/{id}")
String paymentInfoOk(@PathVariable("id") Integer id);
@GetMapping(value = "/payment/hystrix/timeout/{id}")
String paymentInfoTimeOut(@PathVariable("id") Integer id);
}
3、测试降级
Tip:如果不生效,检查该实现类是否以组件的形式添加 Spring 容器中,最常用的方式就是在类上标注 @Service注解或者@Component注解。