梦入琼楼寒有月,行过石树冻无烟

Spring cloud Hystrix 容错处理

Hystrix 是由 Netflix 开源的一个延迟和容错库,旨在隔离对远程系统、服务和第三方库的访问点,他主要通过:“隔离、降级、融断、缓存”,等方式来避免或解决雪崩效应。处理和预防这类问题的项目也很多,通常主流的是 Sentinel、Hystrix、Resillience4j 三类解决方案。

Sentinel

阿里巴巴于 2012年 发布的面向分布式服务架构的轻量级流量控制组件。

Resilience4j

他是一个受 Netfilx Hystrix 启发的轻量级容错库,专门为函数式编程而设计

雪崩效应

雪崩效应也被称之为 “服务雪崩”,而雪崩效应的造成就是 级联 的崩溃,假设服务A调用服务B,服务B调用服务C,服务C调用服务D,这其中任何一个节点无法使用,就会造成下面的服务无法使用,这就会产生级联故障。

如果这种请求很多,那么将会导致不可用的服务越来越多,之后占用的计算机资源也越多,而这些请求则会消耗系统资源,导致其他请求也不可用,这种现象也被称之为:“服务雪崩”。

也就是说当服务提供者导致了服务消费者不可使用,并将问题逐渐放大至微服务系统,进而造成系统崩溃。

造成雪崩原因

雪崩效应的造成原因可以是流量激增、硬件故障、bug、缓存、资源耗尽、线程同步等待、配套资源不可用等:

流量激增

在目前,造成流量激增的方式可以恶意攻击,或者说是爬虫造成的都有可能,这种主要体现在了京东 6·18 活动中。

硬件故障

假设一个集群中,有两个服务提供者,一个服务提供者的硬件损害。那么整个集群就成为了单点应用,导致服务的压力加大,从而出现了服务延迟,由于服务延迟的不断增加,最终导致了雪崩。

bug

通常程序中出现 bug 都是正常的事情,在 spring cloud 中,假设有一个不应该被类使用的接口被调用,则会造成服务之间的互相调用导致的循环逻辑问题,则会可能导致服务雪崩。

缓存问题

缓存问题主要可以分为缓存穿透、缓存击穿、缓存雪崩等三个问题:

缓存穿透

缓存穿透指的是用户不断请求数据库中没有的数据,这将会导致数据库的压力过大。至于这种问题的防范问题,可以通过下述方法解决:

  1. 在路由中增加权限校验
  2. 设置最大(max)与最小(min)的拦截请求(大于等于或小于等于)
缓存击穿

缓存击穿是指 缓存没有数据,但数据库中有数据(通常是缓存时间到期了),当用户在缓存中没有得到数据,就从数据库中获得数据,从而让数据库压力增大,就会造成缓存击穿。

至于预防缓存击穿,那么就就是将 热点数据 设置为永不过期,来解决问题。

缓存雪崩

缓存雪崩说直白一点,就是缓存大量过期,而此时很多用户都是查询缓存中的数据,从而将流量转向数据库,进一步导致数据库宕机。预防这种问题需要通过将缓存数据的过期时间为 随机,以此来防止数据缓存同一时间大量过期。 如果将缓存击穿的问题解决了,那么缓存雪崩的问题也可以适当的预防。

资源耗尽

资源耗尽主要由两种原因造成的原因可以是:“服务提供者不可用,导致服务消费者等待,进而造成资源消耗”,也可是用户的大量流量请求,以及重试流量加大导致的。

线程同步等待

假设系统采用的是服务调用模式,核心服务和非核心服务共用一个线程池以及消息队列,假设一个核心的业务线程调用了一个非核心进程,那么这个非核心进程一旦出现问题,则导致核心线程出现问题。如果核心线程断了,那么将会导致后续的线程也一一崩溃,最终导致雪崩。

配套资源不可用

配套资源不可用可能是数据中心不可用或者电信基础网络服务出现问题,欣慰的是这类事故出现的概率极低。

Hystrix 的解决方式

熔断机制

至于 Hystrix 熔断机制,他主要通过融断器来执行融断的工作,熔断器在现实生活中的运用主要是 “保险丝”。

保险丝(fuse)的在电器的主要作用就是 当电器的电流异常升高到一定热度的时候,那么保险丝自身熔断来切断电流,以此来保护电路的安全运行

快速失败及熔断机制


Hystrix 的熔断器可以实现快速失败,所谓快速失败就是当一个问题达到了某个规则的阀值,则该请求以后调用任何接口都会失败,最后导致不会访问远程服务器,以此来防止应用程序和资源的消耗。

熔断器能够诊断错误是否经过修正,如果已经修正那么熔断器则会关闭,让应用程序在此调用操作。熔断器能够记录最近调用所发生的错误次数,假设熔断器小于阀值,则可以关闭,否则继续开启。

熔断在正常状态下,一般处于关闭状态,如果调用的服务出错值达到莫个阀值,则会让其快速失效,以此来避免大量的无效请求影响系统。

一段时间过后熔断机制将会进入半尝试状态,允许少量请求进行尝试,如果成功则关闭熔断状态,否则将继续持续。

隔离机制

隔离机制主要分为线程池隔离以及信号量隔离模式,在病毒传播中为了防止病毒扩散,最好的方式就是隔离病毒携带者,从而来保护正常的个体。这种方式在分布式系统中也采用隔离的方式进行容错处理。

线程池隔离模式


在 Hystrix 的线程池隔离模式下,会为每一个 依赖建立一个线程池,以此来存储当前的依赖请求,线程池对请求进行处理。 线程池的主要作用就是隔离线程的依赖,以此来限制线程的并发访问和阻塞扩张。

当流量达到峰值的时候,如果不能及时处理的请求将会被存储到线程池后进行处理。运营环境被隔离时,会根据依赖划分多个线程池,以此来进行资源隔离,就算调用的服务代码存在 bug,也不会对系统的其他服务造成影响。

信号量隔离模式


信号量隔离模式主要用于记录当前请求的数量,他内在存在了一个类似 “规则” 的,当信号量小于等于规则内最大的数量时,将会丢弃请求,否则将会执行请求,且对当前的信号量加一。

服务降级

服务降级的主要机制就是 如果一个资源快不够了,则需要将某些服务先关掉,之后等到资源足够时在进行开启,和熔断的目的相差无几,以此来保证上游服务的稳定性。根据业务的不同,降级也分为两种模式,分别为:

Fallback

Fallback 一词也被称之为 “倒退” 的意思,及如果服务失败,那么则会通过 fallback 的方式进行降级。

服务级联模式

服务级联模式则是如果服务失败,则需要调用备用服务,服务级联模式会尽可能的返回数据。

这样的做法如果考虑不充分会造成级联的崩溃,假设在缓存失败后将全部流量导到数据库请求中,那么数据库可能会直接崩溃,因此级联模式也考验开发者的能力

简单来讲服务降级的两种方式分别是分级,将故障的服务丢弃,而服务级联模式则是将请求转移在另一个集群上。

缓存机制

缓存机制就是将请求的所有结果进行缓存,如果有相同的请求发送过来,则直接从缓存中取出结果,以此来减少请求的开销。

Hystrix Fallback 实现


通常回退方法的实现有两种,一种是通过 feign 来进行实现,另一种解决方案则是根据 @HystrixCommand 注解进行实现,在此之前我们需要添加依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

之后在启动类中添加注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* 启动类,分别开启 consul、feign 以及服务熔断
*
* @author kunlun
* @date 2021/7/5
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class DemoApplication {

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

}

尽管 @EnableCircuitBreaker3.0.1 开始 Hystrix 就不在 Spring cloud Netfilx 中了,但是他是唯一一个实现断路器的注解

https://martinfowler.com/bliki/CircuitBreaker.html

application.properties

1
2
3
4
5
6
7
spring.application.name=Hystrix
server.port=9900
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=service-provider
spring.cloud.consul.discovery.register=false
feign.hystrix.enabled=true # 开启 Hystrix

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.demo.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
* 使 Feign 支持 Hystrix,即添加 fallback 处理类,在服务熔断时返回 fallback 类中内容
*
* @author kunlun
* @date 2021/7/08
*/
@FeignClient("service-provider")
public interface MyFeignFallbackClient {

/**
* 添加指定的 fallback 类,在服务熔断时返回 fallback 类内容
* @return
*/
@GetMapping("/hey")
String hey();
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.example.demo.controller;

import com.example.demo.service.MyFeignFallbackClient;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 调用 FeiginFallback 客户端
*
* @author kunlun
* @date 2021/7/5
*/
@RestController
public class HeyController {

@Autowired
private MyFeignFallbackClient myFeignFallbackClient;

@RequestMapping("/hey")
@HystrixCommand(fallbackMethod = "defaultCities")
public String index() {
String fallback = myFeignFallbackClient.hey();
return fallback;
}

public String defaultCities() {
return "No server!!!";
}

}

Hystrix Dashboard (consul)


Hystrix Dashboard 即 “Hystrix 仪表盘”,也是除了容错之外还提供的实时监控功能,他会实时的累加记录所有关于 HystrixCommand 执行的信息,其中包含了每秒执行了多少请求,以及成功或失败等信息。

Hystrix Dashboard 是一款针对 Hystrix 进行实时监控的工具,可以实现出实时监控数据,并直观的显示 Hystrix Command 的请求响应时间、请求成功率等

因为 Hystrix Dashboard 仅仅实现了数据的监控,因此 我们需要在 Hystrix Fallback 的基础上进行扩展,其中启动类需要修改并添加依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix-dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.example.demo;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;

/**
* 启用服务发现以及 feign、Hystrix Dashboard 和服务熔断等注解支持,并配置 Servlet
*
* @author kunlun
* @date 2021/7/6
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrixDashboard
@EnableCircuitBreaker
public class DemoApplication {

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

@Bean
public ServletRegistrationBean servletRegistrantionBean() {
HystrixMetricsStreamServlet hystrixMetricsStreamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(hystrixMetricsStreamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}

application.properties

1
2
3
4
5
6
7
spring.application.name=HystrixDashboard
server.port=9502
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=service-provider
feign.hystrix.enabled=true
hystrix.dashboard.proxy-stream-allow-list=* # 设置仪表板代理流允许列表为所有

当一切完成之后访问 http://localhost:9502/hystrix 并在输入框输入 http://localhost:9502/actuator/hystrix.stream 即可。

Hystrix Turbine 与 Hystrix Dashboard 的区别就是一个只能看到单个应用程序内的服务信息,而另一个则是多个实例集群的状态形式展现,因此 Hystrix Turbine 由此而生。

强烈建议使用 Eureka

⬅️ Go back