在分布式体系中,不同的微服务(应用程序)之间可能有数十个依赖关系
在实际的运行过程中,每个依赖关系的都是有一定的概率出现错误,而导致出错

服务雪崩

在多个微服务之间调用的时候,假设调用关系如下所示:
Hystrix容错处理 - 图1
微服务之间的调用,被称为扇出
如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务B的调用就会占用越来越多的系统资源,进而引起系统崩溃,从而引起“雪崩效应”

服务降级、熔断、限流

服务降级与服务熔断区别
10张图带你彻底搞懂限流、熔断、服务降级 - 腾讯云开发者社区-腾讯云

服务降级

概念:服务降级一般是指在服务器压力剧增的时候,根据实际业务使用情况以及流量,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器资源的资源以保证核心业务的正常高效运行。
原因:服务器的资源是有限的,在用户使用的高峰期,会影响整体服务的性能,严重的情况下会导致核心业务的宕机。所以高峰期的时候为了保证核心功能服务的可用性,就需要对某些服务进行降级处理。
触发降级的情况:

  • 程序运行异常
  • 请求超时
  • 服务熔断造成的降级

    服务熔断

    概念:应对微服务雪崩效应的一种链路保护机制,可以类比为电路中的保险丝
    原因:微服务之间的应用调用是通过远程调用的方式进行实现的,服务A调用B,服务B又去调用服务C,
    如果在这条链路上的服务C的调用时间过长或者服务C直接出现宕机,同时对服务B的请求调用也在不断增加,那么那么服务B就会崩溃,从而服务A也会出现崩溃,从而导致雪崩效应
    类比:类似于在家庭电路中,如果使用的电器电压过高,那么就会导致保险丝的熔断,从而保护电路中的其他设备不被损坏。在微服务架构中,熔断器起到的也是类似的作用。在某些服务不可用或响应时间太长的时候,会进行服务熔断,不再有该服务节点的调用,而是快速返回错误信息,而在检测到微服务节点调用正常之后,就又会恢复正常。

    服务限流

    Hystrix是什么

    Hystrix是一个用于处理分布式系统的延迟容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性

Hystrix微服务构建

提供者微服务模块构建

image.png

  1. import org.springframework.stereotype.Service;
  2. import java.util.concurrent.TimeUnit;
  3. /**
  4. */
  5. @Service
  6. public class PaymentService {
  7. /**
  8. */
  9. public String paymentInfo_OK(Integer id)
  10. {
  11. return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
  12. }
  13. public String paymentInfo_TimeOut(Integer id)
  14. {
  15. try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
  16. return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): 3";
  17. }
  18. }

在Service方法中配置两个方法

  • 一个是正常调用的方法
  • 一个是测试超时的方法

此时我们手动测试接口请求时,无明显请求卡顿感

高并发测试

使用Apache JMeter进行高并发压力测试
官网地址:
Apache JMeter - Apache JMeter™
在JMeter中配置,每秒200个请求,请求100秒,也就是说有2w个请求向我们timeout接口发起请求
这时,如果我们再次访问ok的接口,发现原本立即响应的接口,此时也会需要一定的等待时间才能进行响应
也就是出现卡顿感
此时,我们还是在一个服务内部进行,而如果在不同的服务之间,又是如何?

消费者微服务构建

image.png
在消费者模块中,使用OpenFeign进行远端服务调用
首先对提供者模块的接口进行压力测试,再去访问ok的接口,发现接口的响应速度被拖慢,甚至出现响应超时错误

降级容错

需要处理两个方面的问题

  • 请求超时导致服务器响应变慢:超时不再等待
  • 服务宕机或者运行出错:出错要有处理方案

比如:

  • 对方服务(8001)超时了,调用者(80)不能一直卡死,必须由服务降级
  • 对方服务(8001)宕机了,调用者(80)不能一直卡死等待,必须由服务降级
  • 对方服务(8001)正常,而调用者(80)自身出现故障或者有自我要求(自己的请求等待时间小于服务提供者的处理时间),需要对自己进行降级

    服务提供者降级

    对于服务提供者8001来说,需要对自身进行一定的处理
    设置自身调用超时时间的峰值,在峰值内可以正常运行,超过了峰值时间需要有兜底的方法,作服务降级
    在Hystrix中,使用注解@HystrixCommand注解来进行服务降级
    在需要配置服务降级的方法上,添加注解
    其中,fallbackMethod指出,该方法服务降级后的备用方法 ```java @Service public class PaymentService{

    @HystrixCommand(fallbackMethod = “paymentInfo_TimeOutHandler”/指定善后方法名/,commandProperties = {

    1. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")

    }) public String paymentInfo_TimeOut(Integer id) {

    1. //int age = 10/0;
    2. try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
    3. return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): ";

    }

    //用来善后的方法 public String paymentInfo_TimeOutHandler(Integer id) {

    1. return "线程池: "+Thread.currentThread().getName()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o";

    }

}

  1. 在主启动类上需要添加注解,来启用服务降级:`@EnableCircuitBreaker`<br />这样,当出现超时异常和运行时异常时,会**自动走我们配置的备用方法,从而快速响应,避免阻塞**
  2. <a name="gqEcQ"></a>
  3. ### 服务调用者降级
  4. 服务调用者自身同样需要降级(自身防护)<br />在启动类上添加`@EnableHystrix`<br />在业务代码上添加`@HystrixCommand`
  5. ```java
  6. import com.lun.springcloud.service.PaymentHystrixService;
  7. import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
  8. import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.springframework.web.bind.annotation.GetMapping;
  11. import org.springframework.web.bind.annotation.PathVariable;
  12. import org.springframework.web.bind.annotation.RestController;
  13. import javax.annotation.Resource;
  14. @RestController
  15. @Slf4j
  16. public class OrderHystirxController {
  17. @Resource
  18. private PaymentHystrixService paymentHystrixService;
  19. @GetMapping("/consumer/payment/hystrix/timeout/{id}")
  20. @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
  21. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
  22. })
  23. public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
  24. //int age = 10/0;
  25. String result = paymentHystrixService.paymentInfo_TimeOut(id);
  26. return result;
  27. }
  28. //善后方法
  29. public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
  30. return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
  31. }
  32. }

通过上述两个方式,我们已经学会了如何在服务的提供方和消费方通过服务降级来进行服务保护
但存在如下两个问题:

  • 假设我们有n个方法需要配置服务降级,那是否要写n个备用方法?
    image.png
  • 备用处理方法和业务代码夹杂在一起,代码耦合度较高

    全局服务降级

    通过全局服务降级,来避免傻瓜式为每个方法都要重复写一个备用方法的问题
    通过注解@DefaultProperties来配置统一的服务降级处理方法
    没有配置服务降级的方法就会使用配置的通用方法
    但如果自身已经配置了服务降级方法,就会走配置的方法 ```java import com.lun.springcloud.service.PaymentHystrixService; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 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 OrderHystirxController { @Resource private PaymentHystrixService paymentHystrixService;

  1. @GetMapping("/consumer/payment/hystrix/ok/{id}")
  2. public String paymentInfo_OK(@PathVariable("id") Integer id)
  3. {
  4. String result = paymentHystrixService.paymentInfo_OK(id);
  5. return result;
  6. }
  7. @GetMapping("/consumer/payment/hystrix/timeout/{id}")

// @HystrixCommand(fallbackMethod = “paymentTimeOutFallbackMethod”,commandProperties = { // @HystrixProperty(name=”execution.isolation.thread.timeoutInMilliseconds”,value=”1500”) // }) @HystrixCommand//用全局的fallback方法 public String paymentInfo_TimeOut(@PathVariable(“id”) Integer id) { //int age = 10/0; String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } public String paymentTimeOutFallbackMethod(@PathVariable(“id”) Integer id) { return “我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o”; }

  1. // 下面是全局fallback方法
  2. public String payment_Global_FallbackMethod()
  3. {
  4. return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
  5. }

}

  1. <a name="Khspp"></a>
  2. ### 通配服务降级
  3. **通过通配服务降级,可以将业务逻辑和备用方法处理逻辑分开,从而降级代码之间的耦合度**<br />PaymentFallbackService类实现PaymentHystrixService接口
  4. ```java
  5. import org.springframework.stereotype.Component;
  6. @Component
  7. public class PaymentFallbackService implements PaymentHystrixService
  8. {
  9. @Override
  10. public String paymentInfo_OK(Integer id)
  11. {
  12. return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
  13. }
  14. @Override
  15. public String paymentInfo_TimeOut(Integer id)
  16. {
  17. return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
  18. }
  19. }

PaymentHystrixService接口

  1. import org.springframework.cloud.openfeign.FeignClient;
  2. import org.springframework.stereotype.Component;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.PathVariable;
  5. @Component
  6. @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,//
  7. fallback = PaymentFallbackService.class)//指定PaymentFallbackService类
  8. public interface PaymentHystrixService
  9. {
  10. @GetMapping("/payment/hystrix/ok/{id}")
  11. public String paymentInfo_OK(@PathVariable("id") Integer id);
  12. @GetMapping("/payment/hystrix/timeout/{id}")
  13. public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
  14. }

故意关闭微服务8001
客户端自己调用提示 - 此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。

Hystrix服务熔断理论

熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务出错不可用或者响应的时间太长导致超时后,会进行服务的降级,进而熔断该服务节点的调用,快速返回错误信息。而当检测到服务又恢复可用时,又要恢复调用链路。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。

bliki: CircuitBreaker
image.png
三个状态:

  • 开启:熔断器打开,快速返回错误信息
  • 关闭:熔断器关闭,由具体业务返回信息
  • 半开:尝试恢复业务,如果正常则熔断器关闭,反之熔断器打开
  1. import cn.hutool.core.util.IdUtil;
  2. import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
  3. import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
  4. import org.springframework.stereotype.Service;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import java.util.concurrent.TimeUnit;
  7. @Service
  8. public class PaymentService{
  9. ...
  10. //=====服务熔断
  11. @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
  12. @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
  13. @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
  14. @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
  15. @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
  16. })
  17. public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
  18. if(id < 0) {
  19. throw new RuntimeException("******id 不能负数");
  20. }
  21. String serialNumber = IdUtil.simpleUUID();
  22. return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
  23. }
  24. public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
  25. return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
  26. }
  27. }

涉及到断路器的三个重要参数:

  1. 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  2. 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次7,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  3. 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

图形化Dashboard搭建

image.png
在9001DashBoard服务中启用Dashboard

  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
  4. @SpringBootApplication
  5. @EnableHystrixDashboard
  6. public class HystrixDashboardMain9001
  7. {
  8. public static void main(String[] args) {
  9. SpringApplication.run(HystrixDashboardMain9001.class, args);
  10. }
  11. }

在8001服务中指定监控路径

  1. import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.boot.web.servlet.ServletRegistrationBean;
  5. import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
  6. import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
  7. import org.springframework.context.annotation.Bean;
  8. @SpringBootApplication
  9. @EnableEurekaClient
  10. @EnableCircuitBreaker
  11. public class PaymentHystrixMain8001
  12. {
  13. public static void main(String[] args) {
  14. SpringApplication.run(PaymentHystrixMain8001.class, args);
  15. }
  16. /**
  17. *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
  18. *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
  19. *只要在自己的项目里配置上下面的servlet就可以了
  20. *否则,Unable to connect to Command Metric Stream 404
  21. */
  22. @Bean
  23. public ServletRegistrationBean getServlet() {
  24. HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
  25. ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
  26. registrationBean.setLoadOnStartup(1);
  27. registrationBean.addUrlMappings("/hystrix.stream");
  28. registrationBean.setName("HystrixMetricsStreamServlet");
  29. return registrationBean;
  30. }
  31. }

image.png
image.png
image.png