Spring Cloud 快速入门(四)Hystrix 服务熔断 、服务降级、执行隔离

1. 前置概念

1.1 服务熔断

(1) 雪崩效应
在复杂的系统中,经常会出现 A 依赖于 B,B 依赖于 C,C 依赖于 D,……这种依赖将会产生很长的调用链路,这种复杂的调用链路称为 1->N 的扇出。
在这里插入图片描述

如果在 A 的调用链路上某一个或几个被调用的子服务不可用或延迟较高,则会导致调用A 服务的请求被堵住。
如下图,D’’’‘出问题,导致C’'出问题,导致B’出问题,导致A出问题:
在这里插入图片描述

堵住的 A 请求会消耗占用系统的线程、IO 等资源,当对 A 服务的请求越来越多,占用的计算机资源越来越多的时候,会导致系统瓶颈出现,造成其他的请求同样不可用,最终导致业务系统崩溃,这种现象称为雪崩效应。

(2) 服务雪崩
雪崩效应发生在分布式 SOA 系统(面向服务的架构)中,则称为服务雪崩。
在这里插入图片描述

上图是用户请求的多个服务(A,H,I,P)均能正常访问并返回的情况。

在这里插入图片描述

上图为请求服务 I 出现问题时,一个用户请求被阻塞的情况。

在这里插入图片描述

上图为大量用户请求服务 I 出现异常全部陷入阻塞的的情况,即服务发生雪崩的情况。

短时间内高并发下,在请求超时前,大量请求因为服务I导致阻塞占用系统的线程、IO 等资源,从而造成其他请求不可用,最终导致系统崩溃,即服务雪崩

(3) 熔断机制
熔断机制是服务雪崩的一种有效解决方案。常见的熔断有两种:

  • 预熔断
    根据以往经验,提前预料到在某个时间段里某些服务会出现访问高峰,为了保证消费者对重要的、基本的服务能正常访问,通过服务治理方式,预先把对消费者不重要、不紧急的服务访问权限暂停或延迟
  • 即时熔断
    在非预知情况下,消费者对某个提供者的访问出现问题了,响应慢,超时或者根本没有响应,在指定的时间窗内消费者对某一个提供者请求的失败率达到预先设置的阈值时,为了防止雪崩的发生,消费者自动把提供者的访问链路断开

可以看出,熔断机制是对消费者的一种保护措施

1.2 服务降级

服务降级是请求发生问题后的一种增强用户体验的方式。

发生服务熔断,一定会发生服务降级。但发生服务降级,并不意味着一定是发生了服务熔断(没有发生熔断,只有某一个请求因为网络问题等超时了,也会触发服务降级)。

熔断以后不会发生雪崩,但是对用户体验很差,直接返回用户404,或500,为了提升用户体验,预先设定一些结果返回给用户,这就是服务降级。

服务降级埋点:
在这里插入图片描述

  • 服务路由:直接路由到静态代理服务器,返回静态页面,或者用网关,在服务路由之前先进行筛选,只为50%的请求服务,另外50%直接返回临时性结果:“服务器忙,请重试”。
  • 消费者:消费者不继续向后访问直接返回在本地事先定义好的请求结果
  • 数据缓存层:事先定义好的请求结果没有放在消费者端,而是放在数据缓存层Redis中,从缓存层获取返回
  • 消息中间件:消费者把请求放到消息中间件,让提供者延迟处理,限流削峰,不会超时,只是处理慢一些,并且返回的是真实的结果,只有消息中间件给出的结果是真实的结果
  • 提供者:有可能提供者服务器是ok的,但是访问的DBMS出问题了,所以提供者就直接返回一个结果

2. Hystrix 简介

Spring Cloud 是通过 Hystrix 来实现服务熔断与降级的。

2.1 官网 Wiki

在这里插入图片描述

在这里插入图片描述

【原文】In a distributed environment, inevitably(不可避免地) some of the many service dependencies will fail. Hystrix is a library that helps you control the interactions(交互) between these distributed services by adding latency tolerance(延迟容忍) and fault tolerance logic(容错逻辑). Hystrix does this by isolating points of access between the services, stopping cascading failures(级联错误) across them(跨服务), and providing fallback options(回退选项), all of which improve your system’s overall resiliency(弹性).

【翻译】在分布式环境中,许多服务依赖中的一些服务发生失败是不可避免的。Hystrix 是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的访问点、停止跨服务的级联故障以及提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。

2.2 综合说明

Hystrix 是一种开关装置,类似于熔断保险丝。在消费者端安装一个 Hystrix 熔断器,当Hystrix 监控到某个服务发生故障后熔断器会开启,将此服务访问链路断开。不过 Hystrix 并不会将该服务的消费者阻塞,或向消费者抛出异常,而是向消费者返回一个符合预期的备选响应(FallBack)。通过 Hystrix 的熔断与降级功能,避免了服务雪崩的发生,同时也考虑到了用户体验。故 Hystrix 是系统的一种防御机制。

3. fallbackMethod 服务降级

Hystrix 对于服务降级的实现方式有两种:fallbackMethod 服务降级,与 fallbackFactory服务降级。首先来看 fallbackMethod 服务降级。

总步骤

  • 添加 Hystrix 依赖
  • 修改处理器方法。在处理器方法上添加@HystrixCommond 注解
  • 在处理器中定义服务降级方法
  • 在启动类上添加@EnableCircuitBreaker 注解(或将@SpringBootApplication 注解替换为@SpringCloudApplication 注解)

3.1 创建消费者工程 04-consumer-fallbackmethod-8080

(1) 创建工程
复制第二章的 02-consumer-8080 工程,并重命名为 04-consumer-fallbackmethod-8080。该工程的运行说明 hystrix 本身与 feign 是没有关系的。

(2) 添加 hystrix 依赖

<!--hystrix依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

(3) 修改处理器类
在这里插入图片描述

@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
    
    
    @Autowired
    private RestTemplate restTemplate;
    // 直连提供者
    // private static final String SERVICE_PROVIDER = "http://localhost:8081";
    // 要使用微服务名称来从eureka server查找提供者
    private static final String SERVICE_PROVIDER = "http://abcmsc-provider-depart";
	...

    @HystrixCommand(fallbackMethod = "getHystrixHandler")
    @GetMapping("/get/{id}")
    public Depart getByIdHandler(@PathVariable("id") int id) {
    
    
        String url = SERVICE_PROVIDER + "/provider/depart/get/" + id;
        return restTemplate.getForObject(url, Depart.class);
    }

    // 定义服务降级方法,即响应给客户端的备选方案
    public Depart getHystrixHandler(@PathVariable("id") int id) {
    
    
        Depart depart = new Depart();
        depart.setId(id);
        depart.setName("no this depart");
        return depart;
    }
	...
}

(4) 在启动类添加注解@EnableCircuitBreaker
在这里插入图片描述

Spring Cloud 专门定义了一个组合注解@SpringCloudApplication,包含了服务发现和熔断功能,可以用它直接替换掉这三个注解:

@SpringCloudApplication
//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker
public class ApplicationConsumer8080 {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(ApplicationConsumer8080.class, args);
    }

}

(5) 测试
只启动消费者,或者启动03-provider-8081工程,之前演示过超时,发生超时也会触发服务降级:
在这里插入图片描述

3.2 创建消费者工程 04-consumer-feign-fallbackmethod-8080

(1) 创建工程
复制第三章 03-consumer-feign-8080工程,并重命名为04-consumer-feign-fallbackmethod-8080。该工程是 Hystrix 与 Feign 结合使用。当然,一般情况下都是这样使用的。

(2) 添加 hystrix 依赖

<!--hystrix依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

(3) 修改处理器
在这里插入图片描述

@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
    
    

    @Autowired
    private DepartService service;
	...
	
    // 指定该方法要使用服务降级。即当前处理器方法在运行过程中若发生异常,
    // 无法给客户端正常响应时,就会调用fallbackMethod指定的方法
    @HystrixCommand(fallbackMethod = "getHystrixHandler")
    @GetMapping("/get/{id}")
    public Depart getByIdHandler(@PathVariable("id") int id) {
    
    
        return service.getDepartById(id);
    }

    // 定义服务降级方法,即响应给客户端的备选方案
    // 除了方法名以外,入参回参都要保持一致
    public Depart getHystrixHandler(@PathVariable("id") int id) {
    
    
        Depart depart = new Depart();
        depart.setId(id);
        depart.setName("no this depart");
        return depart;
    }
	...
}

(4) 在启动类添加注解@SpringCloudApplication

// @EnableCircuitBreaker  // 开启熔断器
// @SpringBootApplication
@EnableFeignClients  // 开启Feign客户端
@SpringCloudApplication //组合注解包含了@EnableCircuitBreaker
public class ApplicationConsumer8080 {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(ApplicationConsumer8080.class, args);
    }

}

(5) 测试
只启动消费者,或者启动03-provider-8081工程,上一章演示过超时请求,发生超时也会触发服务降级:
在这里插入图片描述

4. fallbackFactory 服务降级

总步骤

  • 添加 Hystrix 依赖
  • 定义服务降级类
  • 在 Feign 接口中指定服务降级类
  • 修改配置文件,开启 Feign 对 Hystrix 的支持
  • 在启动类上添加@EnableCircuitBreaker 注解(或将@SpringBootApplication 注解替换为@SpringCloudApplication 注解)

4.1 创建消费者工程 04-consumer-fallbackfactory-8080

(1) 创建工程
复制 04-consumer-feign-fallbackmethod-8080 工程,并重命名为 04-consumer-fallbackfactory-8080。

(2) 修改配置文件

在这里插入图片描述

feign:
  # 开启Feign对Hystrix的支持
  hystrix:
    enabled: true

(3) 定义降级处理类
在这里插入图片描述
实现FallbackFactory接口,并且交给Spring容器管理:
在这里插入图片描述

在这里插入图片描述

/**
 * 服务降级类
 * 该类需要实现FallbackFactory接口
 * 其泛型为该服务降级类所对应的Feign接口
 * 
 * FallbackFactory的优先级高于fallbackMethod
 */
@Component
public class DepartFallbackFactory implements FallbackFactory<DepartService> {
    
    
    @Override
    public DepartService create(Throwable throwable) {
    
    
        return new DepartService() {
    
    
            @Override
            public boolean saveDepart(Depart depart) {
    
    
                System.out.println("执行saveDepart()的服务降级处理方法");
                return false;
            }

            @Override
            public boolean removeDepartById(Integer id) {
    
    
                System.out.println("执行removeDepartById()的服务降级处理方法");
                return false;
            }

            @Override
            public boolean modifyDepart(Depart depart) {
    
    
                System.out.println("执行modifyDepart()的服务降级处理方法");
                return false;
            }

            @Override
            public Depart getDepartById(Integer id) {
    
    
                Depart depart = new Depart();
                depart.setId(id);
                depart.setName("no this depart -- class");
                return depart;
            }

            @Override
            public List<Depart> listAllDeparts() {
    
    
                System.out.println("执行listAllDeparts()的服务降级处理方法");
                return null;
            }
        };
    }
}

(4) 修改 Feign 接口
在这里插入图片描述

// 指定当前为Feign客户端,参数为提供者的微服务名称
// fallbackFactory用于指定当前Feign接口的服务降级类
@FeignClient(value = "abcmsc-provider-depart", fallbackFactory = DepartFallbackFactory.class)
@RequestMapping("/provider/depart")
public interface DepartService {
    
    
    @PostMapping("/save")
    boolean saveDepart(@RequestBody Depart depart);

    @DeleteMapping("/del/{id}")
    boolean removeDepartById(@PathVariable("id") Integer id);

    @PutMapping("/update")
    boolean modifyDepart(@RequestBody Depart depart);

    @GetMapping("/get/{id}")
    Depart getDepartById(@PathVariable("id") Integer id);

    @GetMapping("/list")
    List<Depart> listAllDeparts();
}

(5) 演示
在这里插入图片描述

此时类级别的优先级高

4.2 创建消费者工程 04-consumer-fallbackfeign-8080

在这里插入图片描述

(1) 创建工程
复制 04-consumer-fallbackfactory-8080 工程,并重命名为 04-consumer-fallbackfeign-8080。

(2) 定义降级处理类
在这里插入图片描述

@Component
@RequestMapping("/fallback/consumer/depart")
//注意该@RequestMapping不是必须的,主要是因为
//我们在DepartService接口中也写了@RequestMapping
//如果不指定,mapping映射关系会冲突。
public class DepartFallback implements DepartService {
    
    

    @Override
    public boolean saveDepart(Depart depart) {
    
    
        System.out.println("执行saveDepart()的服务降级方法 - class");
        return false;
    }

    @Override
    public boolean removeDepartById(Integer id) {
    
    
        System.out.println("执行removeDepartById()的服务降级方法 - class");
        return false;
    }

    @Override
    public boolean modifyDepart(Depart depart) {
    
    
        System.out.println("执行modifyDepart()的服务降级方法 - class");
        return false;
    }

    @Override
    public Depart getDepartById(Integer id) {
    
    
        System.out.println("执行getDepartById()的服务降级方法 - class");
        Depart depart = new Depart();
        depart.setId(id);
        depart.setName("no this depart -- class");
        return depart;
    }

    @Override
    public List<Depart> listAllDeparts() {
    
    
        System.out.println("执行listAllDeparts()的服务降级方法 - class");
        return null;
    }
}

(3) 修改 Feign 接口
将原来的 fallbackFactory 属性更换为 fallback 属性。
在这里插入图片描述

// 指定当前为Feign客户端,参数为提供者的微服务名称
// fallback用于指定当前Feign接口的服务降级类
@FeignClient(value = "abcmsc-provider-depart", fallback = DepartFallback.class)
@RequestMapping("/provider/depart")
public interface DepartService {
    
    
    @PostMapping("/save")
    boolean saveDepart(@RequestBody Depart depart);

    @DeleteMapping("/del/{id}")
    boolean removeDepartById(@PathVariable("id") Integer id);

    @PutMapping("/update")
    boolean modifyDepart(@RequestBody Depart depart);

    @GetMapping("/get/{id}")
    Depart getDepartById(@PathVariable("id") Integer id);

    @GetMapping("/list")
    List<Depart> listAllDeparts();
}

(4) 演示
在这里插入图片描述

5. Hystrix 高级属性配置

演示一个有意思的问题:
启动04-consumer-fallbackfactory-8080工程,这个是利用@FeignClient的fallbackFactory属性指定降级类的,同时也有方法级别的降级,之前运行结果是类的级别优先级高
在这里插入图片描述
测试结果:
在这里插入图片描述
此时类级别优先级高

启动03-provider-8081工程,这个工程的Service方法在上一章我们设置过线程阻塞
当时是为了演示Feign的超时设置的:
在这里插入图片描述
现在我们将04-consumer-fallbackfactory-8080工程的连接超时时间改成5秒:
在这里插入图片描述
此时应该是不超时的,但是我们添加了Hystrix依赖,导致如下情况:
在这里插入图片描述
添加了Hystrix后,看到不仅超时了,而且变成方法级别的服务降级了

要搞清楚为什么,需要了解很多东西,先看Hystrix 官网属性配置介绍:
在这里插入图片描述
在这里插入图片描述
首先看执行隔离中的一个配置:
在这里插入图片描述
在这里插入图片描述
我们在当前工程中,将该参数改成4秒:
在这里插入图片描述
看到结果不在超时了:
在这里插入图片描述
这个设置是设置所有的hystrix command的超时时间,是全局配置
如果想要局部配置:
在这里插入图片描述
这个是hystrix的官方配置,整合到SpringCloud以后,就需要使用@HystrixCommand注解的commandProperties属性进行配置:
在这里插入图片描述
注意其中name值去掉了"hystrix.command.HystrixCommandKey"前缀。
SpringCloud中对Hystrix Command相关默认配置,维护在了com.netflix.hystrix.HystrixCommandProperties这个类
在这里插入图片描述
现在重新启动04-consumer-fallbackfactory-8080工程,看到又超时了,说明配置起效了:
在这里插入图片描述

5.1 执行隔离策略

对依赖的请求数量进行限制的这种机制,称为执行隔离。
执行隔离策略有两大作用:防止服务熔断,防止服务雪崩。

(1) 类型
隔离请求的方式有两种类型:

  • 线程隔离:Hystrix 的默认隔离策略。系统会创建一个依赖线程池,为每个依赖请求分配一个独立的线程,而每个依赖所拥有的线程数量是有上限的。当对该依赖的调用请求数量达到上限后再有请求,则直接拒绝该请求,并对该请求做降级处理。所以对某依赖的并发量取决于为该依赖所分配的线程数量。
  • 信号量隔离:对依赖的调用所使用的线程仍为请求线程,即不会为依赖请求再新创建新的线程。但系统会为每种依赖分配一定数量的信号量,而每个依赖请求分配一个信号。当对该依赖的调用请求数量达到上限后再有请求,则直接拒绝该请求,并直接对该请求做降级处理。所以对某依赖的并发量取决于为该依赖所分配的信号数量。

图演示:

  • 官方图:
    在这里插入图片描述
    在这里插入图片描述

  • 执行隔离策略之Thread隔离原理示意图:
    线程隔离会为依赖专门创建一个线程池,假如线程池有20个线程:
    在这里插入图片描述

    正常请求访问会从线程池拿到线程访问依赖A实例:
    在这里插入图片描述
    如果已经有20个请求正在访问了,再有请求过来,直接服务降级:
    在这里插入图片描述

  • 执行隔离策略之Semaphore(信号量)隔离原理示意图
    信号量隔离不存在信号量池,但是为了容易理解和线程隔离对比,图中画了一个信号量池,实际上信号量就是一个整型数,比如20,来一个请求-1,完成一个请求+1即可
    在这里插入图片描述

(2) 对比

在这里插入图片描述

【翻译】通常,只有在调用量非常大(每个实例每秒数百个)以致于单独线程的开销太高时,才应该对HystrixCommands使用信号量隔离;这通常只适用于非网络调用。(简单理解就是:单体应用高并发情况下用信号量隔离

  • 线程是进程的一个执行体,其具有独立运行的特性,而信号量却不是,其仅仅是线程执行的条件。
  • 线程隔离中请求线程与提供者调用线程不是同一个线程,而信号量隔离中请求线程与调用线程是同一个线程。
  • 线程隔离的执行效率要高于信号量隔离的,因为线程隔离的执行体数量是信号量隔离的 2 倍。
  • 线程隔离使每台主机处理请求的数量是有限制的,因为主机线程数量是有上限的。而信号量隔离不同,其没有上限,因为所谓信号量就是一个计数器,是一个数值,其不存在上限。
  • 在服务器少而请求并发量大的情况下不建议使用线程隔离,否则可能会使系统对请求的并发能力下降。
  • 线程隔离便于控制反馈给客户端的降级时间。
    因为信号量隔离的超时控制点只有一处:
    在这里插入图片描述

(3) 修改策略
在这里插入图片描述

若是在配置文件中,则可以通过以下设置修改:

  • hystrix.command.default.execution.isolation.strategy=thread
  • hystrix.command.default.execution.isolation.strategy=semaphore

若是在代码中,则可通过以下语句修改。

  • HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)
  • HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

(4) 默认值
在 HystrixCommandProperties 类的构造器中设置有这些高级属性的默认值。
在这里插入图片描述

5.2 执行隔离其它属性

在这里插入图片描述

(1) 线程执行超时时限
在默认的线程执行隔离策略中,关于线程的执行时间,可以为其设置超时时限。当然,首先通过下面的属性开启该超时时限,该属性默认是开启的,即默认值为 true。若要关闭,则可以配置文件中设置该属性的值为 false。

hystrix.command.default.execution.timeout.enabled
在这里插入图片描述

在开启了执行线程超时时限后,可以通过以下属性设置时限长度。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds

其默认值为 1000 毫秒。这就是前面引入时为什么 sleep(3)是也报超时异常的原因,只要超过了 1 秒就会超时。
在这里插入图片描述

(2) 超时中断
当线程执行超时时是否中断线程的执行。默认为 true,即超时即中断。通过以下属性进行设置。
hystrix.command.default.execution.isolation.thread.interruptOnTimeout

(3) 取消中断
在线程执行过程中,若请求取消了,当前执行线程是否结束呢?由该值设置。默认为 false,即取消后不中断。通过以下属性进行设置。
hystrix.command.default.execution.isolation.thread.interruptOnCancel

(4) 信号量数量
若采用信号量执行隔离策略,则可通过以下属性修改信号量的数量,即对某一依赖所允许的请求的最高并发量。
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests

5.3 服务降级属性

在这里插入图片描述

(1) 降级请求最大数量
该属性仅限于信号量隔离。当信号量已用完后再有请求到达,并不是所有请求都会进行降级处理,而是在该属性设置值范围内的请求才会发生降级,其它请求将直接拒绝。
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests

(2) 服务降级开关
无论是线程隔离还是信号量隔离,当请求数量到达其设置的上限后再有请求到达是否会对请求进行降级处理,取决于该属性值的设置。若该属性值设置为 false,则不进行降级,而是直接拒绝请求。
hystrix.command.default.fallback.enabled

5.4 服务熔断属性

在这里插入图片描述

(1) 熔断功能开关
设置当前应用是否开启熔断器功能,默认值为 true。
hystrix.command.default.circuitBreaker.enabled

(2) 熔断器开启阈值
当在时间窗内(10 秒)收到的请求数量超过该设置的数量后,将开启熔断器。默认值为 20。
注意,开启熔断器是指将拒绝所有请求;关闭熔断器是指将使所有请求通过。
hystrix.command.default.circuitBreaker.requestVolumeThreshold

(3) 熔断时间窗
当熔断器开启该属性设置的时长后,会尝试关闭熔断器,以恢复被熔断的服务。默认值为 5000 毫秒。
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds

(4) 熔断开启错误率
当请求的错误率高于该百分比时,开启熔断器。默认值为 50,即 50%。
hystrix.command.default.circuitBreaker.errorThresholdPercentage

(5) 强制开启熔断器
设置熔断器无需条件开启,拒绝所有请求。默认值为 false。
hystrix.command.default.circuitBreaker.forceOpen

(6) 强制关闭熔断器
设置熔断器无需条件的关闭,通过所有请求。默认值为 false。
hystrix.command.default.circuitBreaker.forceClosed

5.5 线程池相关属性

关于执行线程的线程池,可以通过以下的这些属性设置。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_41947378/article/details/109025572