Version: Next

服务降级 Fallback

解决

  • 生产者服务 8001 超时 了:消费者调用方 80 不能一直卡死等待,必须有 服务降级
  • 生产者服务 8001 宕机 了:消费者调用方 80 不能一直卡死等待,必须有 服务降级
  • 生产者服务 8001 正常:消费者调用方 80 自己出故障或自己有要求(如自身等待时间小于服务提供者响应时间)80 自身 必须有 服务降级

生产者微服务 provider8001

自身存在问题的解决

  • 设置 自身调用超时时间,在设定时间可以 正常运行
  • 超过设置时间,需要有 兜底方法,做 服务降级 Fallback

业务类

Service 实现类 使用 @HystrixCommand 注解,其具有如下属性

  • fallbackMethod:指定要触发的兜底方法,兜底方法的形参必须和 @HystrixCommand 注释的方法形参一致
  • commandProperties:触发条件,捕获什么异常时执行兜底方法
    • @HystrixProperty 来指定具体属性,如 超时触发
      • name:属性名,如 超时
      • value:属性值,如 3000,表示超过 3 秒就认为超时
  • 由于设置了触发降级的超时时间为 3 秒,所以我们将超时方法中原先的睡 3 秒改为 5 秒,这样就一定可以触发设定的服务降级条件
Service 实现类:在可能出现问题的方法上使用注解,指定兜底方法,设定什么情况下触发兜底
@Service
public class PaymentServiceImpl implements PaymentService {
@Override
public String paymentInfo_OK(Integer id) {
return "线程池:" + Thread.currentThread().getName() +
"payment_OK, id:" + id + "\tO(∩_∩)O哈哈~";
}
@Override
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id) {
int timeNum = 5;
// 强行睡5秒钟
try {
TimeUnit.SECONDS.sleep(timeNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() +
"payment_TimeOut, id:" + id + "\tO(∩_∩)O哈哈~ 耗时 " + timeNum + " 秒钟";
}
// 8001 自身超时——服务降级,兜底方法,在上面用 @HystrixCommand 注定
public String paymentInfo_TimeOutHandler(Integer id) {
return "/(ToT)/调用支付接口超时或异常、\t" + "\t当前线程池名字" + Thread.currentThread().getName();
}
}

主启动类

  • 在主启动类添加 @EnableCircuitBreaker 注解
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}

测试

  • 访问 localhost:8001/payment/hystrix/timeout/2

  • 分析:这个方法被我们设定为需要执行 5 秒,但是我们加了 8001 自身超时服务降级,在第 3 秒,触发,并执行兜底方法,所以应当看到兜底方法的执行结果

执行结果 成功触发服务降级并执行兜底方法
/(ToT)/调用支付接口超时或异常、 当前线程池名字HystrixTimer-1
注意

实际上这里除了设定的超时触发兜底方法,此处被 @HystrixCommand 注解的方法内发生其他异常(如被除数为0异常)也会触发兜底方法,只是用 @HystrixProperty 可以准确的指定超时时间


消费者 consumer80

注意

Hystrix 服务降级可以在 客户端 实现,也可以在 服务端 实现

  • 但一般在 客户端(消费者方) 实现

cloud-consumer-hystrix-order80 也要更好的保护自己,实现 客户端降级保护

  • @HystrixComman 代码的修改建议不使用热部署,而是直接重启微服务

主启动类

  • 添加 @EnableHystrix 注解,它包含了 @EnableCircuitBreaker 注解
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}

业务类

还是使用 @HystrixCommand 注解,如法炮制,这里设定为超过 2.5 秒就认为自己调用的服务方微服务出毛病了,在客户端消费者端触发服务降级

  • 写在 Controller
  • consumer-order80 的服务层只有接口,用 OpenFeign 调用 provider8001 微服务的实际业务逻辑

Controller

cloud-consumer-hystrix-order80 Controller
@RestController
public class PaymentHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2800") // 只等待 2.8 秒
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return paymentHystrixService.paymentInfo_TimeOut(id);
}
// 超时——服务降级兜底方法
private String paymentInfo_TimeoutFallbackMethod() {
return "/(ToT)/我是消费者80,调用8001支付系统繁忙,请10秒钟后重新尝试、\t";
}
}

测试

  • 访问 http://127.0.0.1:80/consumer/payment/hystrix/timeout/2


YAML 坑

注意:坑

可以触发我们在 客户端80 写的服务降级兜底方法

  • 但可以看到触发消耗的时间大约为 1 秒,跟我们设定的 2.8 秒不符
  • 因为 OpenFeign 远程调用的超时控制是基于 Ribbon 的,Ribbon 的默认远程调用超时时间我们需要改大一点,改到 5
  • 需要开启 feign.hystrix.enable=true,否则后面全局服务降级会出问题
  • 还要修改 hystrix 的超时时间,也改大一点
cloud-consumer-feign-hystrix-order80 配置文件:添加 ribbon 超时时间设置
server:
port: 80
#这里只把feign做客户端用,不注册进eureka
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: false
service-url:
#defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka/
#feign: # 客户端 容错限流
# hystrix:
# enabled: true
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 5000
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的实际
ConnectTimeout: 5000
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000

现在客户端触发超时服务降级的时间符合我们的设定


全局服务降级

目前写法的问题:

  • 每个业务方法都对应一个兜底方法,而且混在一起写,耦合度高,方法变多
  • 希望有自定义的兜底方法,也希望有公用的统一的兜底方法

默认服务降级方法

直接在 客户端80 Controller 上加全局默认兜底注解

  • @DefaultProperties(defaultFallback = "") 注定一个全局默认服务降级兜底方法
  • 只写 @HystrixCommand 注解,而不写具体 fallbackMethod 方法的 Controller 方法,都用默认兜底方法来兜底
    • 默认兜底方法不能有形参
    • 现在 time_out 方法不再设置超时,而是写一个立即触发的异常
    • 观察异常发生后,全局服务降级兜底方法能否被触发
  • 专门写了 @HystrixCommand 注解的具体 fallbackMethod 方法的 Controller 方法,按照 fallbackMethod 的内容找兜底方法
order80 Controller
@RestController
@DefaultProperties(defaultFallback = "paymentInfo_Global_FallbackMethod") //统一的降级配置 没有单独的降级 默认使用此个降级方法
public class PaymentHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2800")
})*/
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
int i = 10 / 0; // 埋一个异常,观察全局服务降级兜底方法的触发
return paymentHystrixService.paymentInfo_TimeOut(id);
}
// 超时——服务降级兜底方法
private String paymentInfo_TimeoutFallbackMethod(Integer id) {
return "/(ToT)/我是消费者80,调用8001支付系统繁忙,请10秒钟后重新尝试、\t";
}
// 下面是全局fallback方法
private String paymentInfo_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试, /(ToT)/";
}
}

业务代码降级代码分离

通配服务降级 FeignFallBack

模拟场景

  • 客户端调服务端,遇到服务端直接宕机
  • 客户端80 完成服务降级处理
  • 只需要为 OpenFeign 客户端定义的接口添加一个 服务降级处理实现类 即可实现解耦

未来可能遇到的三大类异常: 运行时异常超时宕机

目前的 cloud-consumer-feign-hystrix-order80 的 controller 里的方法:

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2800")
})*/
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
int i = 10 / 0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
// 超时——服务降级兜底方法
private String paymentInfo_TimeoutFallbackMethod(Integer id) {
return "/(ToT)/我是消费者80,调用8001支付系统繁忙,请10秒钟后重新尝试、\t";
}
  • 正常的 controller 接口方法和服务降级的兜底方法写在一起,高耦合,乱

修改 cloud-consumer-feign-hystrix-order80

  • 目前这个客户端的 service 层有一个 OpenFeign 远程调用服务端的 PaymentHystrixService 接口
  • 重新新建一个类 PaymentFallbackService 实现上述 OpenFeign Service 接口,统一为接口里的方法进行异常处理
/**
* 服务降级统一异常兜底处理
* 实现 OpenFeign 的service接口
*/
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "--- PaymentFallbackService paymentInfo_OK 方法 服务降级: /(ToT)/ ";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "--- PaymentFallbackService paymentInfo_TimeOut 方法 服务降级: /(ToT)/ ";
}
}
  • OpenFeign 远程调用服务端的 PaymentHystrixService 接口中,@FeignClient 注解指定 fallback 属性为刚才写的统一服务降级兜底 Service 实现类
    • OpenFeign 中的远程调用出错时,触发统一兜底方法
OpenFeign Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

测试

访问 http://127.0.0.1:80/consumer/payment/hystrix/ok/2

  • 响应:线程池:http-nio-8001-exec-9payment_OK, id:2 O(∩_∩)O哈哈~

访问 http://127.0.0.1:80/consumer/payment/hystrix/timeout/2

  • 因为里面写了个 10 / 0 必定异常,然后触发 controller 里写的 默认兜底方法
  • 响应:Global异常处理信息,请稍后再试, /(ToT)/

provider8001 直接关了,模拟服务端宕机

  • 访问 http://127.0.0.1:80/consumer/payment/hystrix/ok/2
    • 应该触发 OpenFeign 通配服务降级
    • 响应:--- PaymentFallbackService paymentInfo_OK 方法 服务降级: /(ToT)/
  • 把 order80 controller 里的 10 / 0 异常去掉,然后访问 http://127.0.0.1:80/consumer/payment/hystrix/timeout/2
    • 应该触发 OpenFeign 通配服务降级
    • 响应:--- PaymentFallbackService paymentInfo_TimeOut 方法 服务降级: /(ToT)/