提示:本文主要对Hystrix的服务降级进行总结
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
本文主要对Hystrix的服务降级进行总结
提示:以下是本篇文章正文内容
一、服务降级
关键字:
@HystrixCommand
8001先从自身找问题:设置自身调用超时时间的峰值,
峰值内可以正常运行,超过了需要有兜底的方法处理,
作服务降级fallback
8001设置自身调用超时时间的峰值,峰值内可以正常运行,
超过了需要有兜底的方法处理,作为服务降级的fallback。
1.1 8001 fallback
业务类启用:@HystrixCommand报异常后如何处理?
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod指定的方法。
8001 service
1.1.1 业务类启用
8001 service
PaymentService.java:
package com.atguigu.springcloud.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
/**
* 正常访问,肯定没有问题的方法
* @param id
* @return
*/
public String paymentInfo_OK(Integer id) {
return "线程池: "+Thread.currentThread().getName() + " paymentInfo_OK,id: " +id+ "\t"+ "^_^";
}
/**
* 超时访问,演示降级
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = { //fallbackMethod:兜底方法
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") //线程执行超时时间为3s,3s以内走正常逻辑,超过3s,调用指定方法
})
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 3;
//暂停几秒钟
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName() + " paymentInfo_TimeOut,id: " +id+ "\t"+ "0_0 耗时: " +timeNumber+ "秒钟";
}
/**
* 超时访问的降级方法
*
* @param id
* @return
*/
public String paymentInfo_TimeOutHandler(Integer id) {
return "调用支付接口超时或者异常:\t" + "\t当前线程:"+Thread.currentThread().getName() + " paymentInfo_TimeOutHandler, id: " +id;
}
}
@HystrixCommand报异常后如何处理?
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的
fallbackMethod调用类中的指定方法
图示:
上图故意制造两个异常:
- int age = 10/0; 计算异常
- 我们能接受3秒钟,它运行5秒钟,超时异常
当前服务不可用了,做服务降级,兜底的方案都是 paymentInfo_TimeOutHandler
测试:给主启动类添加注解后再测试
1、没有异常时,访问http://localhost:8001/payment/hystrix/timeout/666
等待5s后会出现结果
2、添加异常,访问http://localhost:8001/payment/hystrix/timeout/666
立即返回结果,没有等待,同样走了兜底的方法。
1.1.2 主启动类激活
主启动类激活:添加新注解@EnableCircuitBreaker
激活:
PaymentHystrixMain8001.java
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient //注册进euraka
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
1.2 80 fallback
Hystrix 可以放在消费端,也可以放在服务端,一般是放在客户端。
80微服务,也可以更好地保护自己,依样画葫芦,照8001进行客户端降级保护。
1.2.1 application.yml
server:
port: 80
eureka:
client:
register-with-eureka: false #不将自己注册进eureka
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
feign:
hystrix:
enabled: true #开启支持hystrix 为什么刚才8001服务端没有开启这个属性,因为服务端没有使用feign
# 之前controller在OrderHystrixController @HystrixCommand配置的超时时间未生效
# 关键在于feign:hystrix:enabled:true 官网解释 "Feign将使用断路器包装所有方法", 也就是将@FeignClient标记的那个service接口下所有的方法都进行了
# hystrix包装, 类似于在所有的方法上面加了@HystrixCommand, 这些方法会应用一个默认的超时时间为1s, 所以service方法上相当于还有一个1s的超时时间
# 1s就会报异常, controller 立马进入备用方法, controller上其他的设置的3秒超时的方法就没有效果了
# 改变这个默认的超时时间方法 如下:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 # 将默认的1s超时修改
# 这里设置的timeoutInMilliseconds会与方法上的注解设置的超时时间比较, 取最小值, 同时也会算上设置的ribbon的时间, 也就是三者的最小值
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
ReadTimeout: 15000 #指建立连接后从服务读取到可用资源所用的时间
ConnectTimeout: 15000 #指建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
# 设置feign客户端的超时时间,用了@FeignCLient注解,默认会优先触发feign的过期时间而报错,走兜底方法。但是开启了
# feign:hystrix:enabled:true,设置的ribbon的超时时间就没用了,改为了默认的1s
1.2.2 主启动类
主启动加注解@EnableHystrix 该注解中包含了@EnableCircuitBreaker
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients //激活feign
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
1.2.3 业务类
将服务提供者 8001的超时时间进行修改一下,设置paymentInfo_TimeOut方法需要执行5秒,超时时间为7秒,服务端8001超时便进行服务降级执行paymentInfo_TimeOutHandler
方法
PaymentService.java:
package com.atguigu.springcloud.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
/**
* 正常访问,肯定没有问题的方法
* @param id
* @return
*/
public String paymentInfo_OK(Integer id) {
return "线程池: "+Thread.currentThread().getName() + " paymentInfo_OK,id: " +id+ "\t"+ "^_^";
}
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = { //fallbackMethod:兜底方法
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "7000") //线程执行超时时间为7s,7s以内走正常逻辑,超过7s,调用指定方法
})
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 5;
//int age = 10/0;
//暂停几秒钟
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName() + " paymentInfo_TimeOut,id: " +id+ "\t"+ "0_0 耗时: " +timeNumber+ "秒钟";
}
public String paymentInfo_TimeOutHandler(Integer id) {
return "调用8001支付接口超时或者异常:\t" + "\t当前线程:"+Thread.currentThread().getName() + " paymentInfo_TimeOutHandler, id: " +id;
}
}
直接调用服务8001的接口,因为paymentInfo_TimeOut需要执行5s,而设置的超时时间为7s,所以直接访问服务端8001不会服务降级。
下面使用order80消费者进行服务调用:
Order80 controller
OrderHystrixController.java:
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.OrderHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private OrderHystrixService orderHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = orderHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "OrderTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") //客户端只等待服务端1.5s
})
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = orderHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String OrderTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80, 对方支付系统繁忙请稍后再试或者自己运行出错请检查自己-.-";
}
@GetMapping("/consumer/payment/hystrix/timeout2/{id}")
@HystrixCommand(fallbackMethod = "OrderTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
})
public String paymentInfo_TimeOut2(@PathVariable("id") Integer id){
String result = orderHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
该controller
中,有paymentInfo_TimeOut、paymentInfo_TimeOut2
两个方法。
paymentInfo_TimeOut
设置的超时时间为2s,且配置文件中设置的@FeignClient
标注的service
中所有方法超时时间为6s(默认为1s),以及ribbon超时时间为15秒,所以该方法的超时时间为2s。由于所调用服务8001的方法需要执行5s,所以当访问http://localhost:8001/payment/hystrix/timeout/8887
时会超时,进而服务降级调用OrderTimeOutFallbackMethod
方法。
1.2.4 验证
验证:
访问:http://localhost/consumer/payment/hystrix/timeout/8887
两秒后返回该信息
而paymentInfo_TimeOut2
方法在注解中设置的超时时间为6s,综合配置文件的设置,该方法的超时时间为6s。由于所调用服务8001的方法需要执行5s,所以当访问http://localhost:8001/payment/hystrix/timeout2/666
时不会超时,消费端和服务端都不会服务降级。
验证:http://localhost/consumer/payment/hystrix/timeout2/666
5s后返回信息:
1.2.5 说明
是否调用兜底fallback
方法是取决于 @HystrixProperty
中的 value = “4000”,只要这个value的值大于服务端8001的睡眠时间或进来就直接异常,比如说 10/0 就会走80的fallback方法,与application.yml
中配置的ribbon的那个feign的超时时间以及hystrix的时间无关
p55,controller中超时时间配置不生效原因:
关键在于feign:hystrix:enabled: true的作用,官网解释“Feign将使用断路器包装所有方法”,也就是将@FeignClient标记的那个service接口下所有的方法进行了hystrix包装(类似于在这些方法上加了一个@HystrixCommand),这些方法会应用一个默认的超时时间为1s,所以你的service方法也有一个1s的超时时间,service1s就会报异常,controller立马进入备用方法,controller上那个3秒那超时时间就没有效果了。
改变这个默认超时时间方法:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
然后ribbon的超时时间也需加上
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
1.2.6 目前问题
1.每个业务方法对应一个兜底的方法,代码膨胀
2.公共的兜底方法和自定义的兜底方法需要分开
1.2.6 解决代码膨胀问题
feign接口系列
@DefaultProperties(defaultFallback = “”)
在类上标注@DefaultProperties(defaultFallback = “”)注解设置类中所有方法的默认公共兜底方法。若单独设置了兜底方法的则调用自己设置的,否则调用该注解设置的兜底方法。
1:1 每个方法配置一个服务降级方法,技术上可以,实际上傻Ⅹ
1:N 除了个别重要核心的业务有专属,其他普通的可以通过
@DefaultProperties(defaultFallback = “”) 统一跳转到统一处理结果页面。
通用的和专属的降级方法分开,避免了代码膨胀,合理减少了代码量。
controller配置
设置全局服务降级方法并在controller上打上注解@DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”)
服务过期时间仍写在方法上的注解中
OrderHystrixController.java:
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.OrderHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private OrderHystrixService orderHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = orderHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
//@HystrixCommand(fallbackMethod = "OrderTimeOutFallbackMethod", commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") //客户端只等待服务端1.5s
// })
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
}) //类加了@DefaultProperties注解,该方法上没有写具体方法名,就会调用@DefaultProperties配置的全局服务降级方法
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = orderHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String OrderTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80, 对方支付系统繁忙请稍后再试或者自己运行出错请检查自己-.-";
}
@GetMapping("/consumer/payment/hystrix/timeout2/{id}")
@HystrixCommand(fallbackMethod = "OrderTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
})
public String paymentInfo_TimeOut2(@PathVariable("id") Integer id){
String result = orderHystrixService.paymentInfo_TimeOut(id);
return result;
}
//全局fallback 全局方法不能有参数,否则会报找不到方法的异常
public String payment_Global_FallbackMethod() {
return "Global异常信息处理,请稍后再试";
}
}
测试结果:
1.2.7 解决服务降级方法和业务逻辑混在一起的问题
服务降级,客户端去调用服务端,碰上服务端宕机或关闭
本次案例服务降级处理是在客户端80完成实现的,与8001没有关系。只需要为Feign客户端定义的接口 (打了@FeignClient注解的接口) 添加一个服务降级处理的实现类即可实现解耦。
未来需要面对的异常:
1.运行时异常
2.超时
3.宕机
修改cloud-consumer-feign-hystrix-order80:
1.根据cloud-consumer-feign-hystrix-order80已经有的OrderHystrixService接口,重新新建一个类(OrderFallbackService)实现该接口,统一为接口里面的方法进行异常处理。
新建OrderFallbackService实现OrderHystrixService接口:
OrderFallbackService.java:
package com.atguigu.springcloud.service;
import org.springframework.stereotype.Component;
@Component
public class OrderFallbackService implements OrderHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "------OrderFallbackService ----paymentInfo_OK fallback";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "------OrderFallbackService ----paymentInfo_TimeOut fallback";
}
}
实现了@FeignClient注解的接口,在该注解中添加 fallback = OrderFallbackService.class
OrderHystrixService.java:
package com.atguigu.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = OrderFallbackService.class) //接口+@FeignClient注解
public interface OrderHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
80的controller没改变:
OrderHystrixController.java:
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.OrderHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private OrderHystrixService orderHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = orderHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
//@HystrixCommand(fallbackMethod = "OrderTimeOutFallbackMethod", commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") //客户端只等待服务端1.5s
// })
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
}) //类加了@DefaultProperties注解,该方法上没有写具体方法名,就会调用@DefaultProperties配置的全局服务降级方法
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = orderHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String OrderTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80, 对方支付系统繁忙请稍后再试或者自己运行出错请检查自己-.-";
}
@GetMapping("/consumer/payment/hystrix/timeout2/{id}")
@HystrixCommand(fallbackMethod = "OrderTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
})
public String paymentInfo_TimeOut2(@PathVariable("id") Integer id){
String result = orderHystrixService.paymentInfo_TimeOut(id);
return result;
}
//全局fallback 全局方法不能有参数,否则会报找不到方法的异常
public String payment_Global_FallbackMethod() {
return "Global异常信息处理,请稍后再试";
}
}
测试:
依次启动7001、8001、80
正常访问测试:http://localhost/consumer/payment/hystrix/ok/1
故意关闭微服务8001,客户端自己调用提示,此时服务端provider已经down了,但是我们做了服务降级处理,
让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器
tips:服务端宕机会调用这个兜底的实现类中的方法,但是客户端中的方法出错还是会调用方法头上那个注解
http://localhost/consumer/payment/hystrix/timeout/667 、http://localhost/consumer/payment/hystrix/timeout2/1
经过自己测试,客户端设置专属服务降级方法,若不关闭服务端,能正常调用服务。在将服务端服务关闭后会使用OrderFallbackServic 实现OrderHystrixService接口的服务降级(服务层面)的降级。
如客户端设置专属降级方法,且客户端在服务端没有关闭时调用服务会超时进行的降级是调用专属(控制层面)方法。关闭服务后再调用服务也是调用专属服务降级(控制层面)方法。
通用的服务降级方法(控制层面)无论服务端是否关闭都是优先调用的。
不会超时,正常情况下客户端对服务端服务调用:
由于服务关闭:服务层面降级
未关闭务端也会超时时,调用服务,降级调用专属方法(控制层面专属服务降级)
关了服务端后,调用服务降级方法也为专属方法(控制层面专属服务降级):