目录
雪崩效应 概述
雪崩效应
1、假设因为某些原因导致服务提供者的响应非常缓慢,消费者对提供者的请求被强制等待,直到服务返回,在高负载场景下,如果不做任何处理,这种问题很可能造成所有处理用户请求的线程都被耗竭,而不能响应用户的进一步请求。
2、在徽服务架构中通常会有多个服务层调用,大量的微服务通过网络进行通信,从而支撑起整个系统。各个微服务之间也难免存在大量的依赖关系,然而任何服务都不是 100% 可用的,网络往往也是脆弱的,所以难免有些请求会失败。 基础服务的故障导致级联故障,进而造成了整个系统的不可用,这种现象被称为服务雪崩效应。服务雪崩效应描述的是一种因服务提供者的不可用导致服务消费者的不可用,并将不可用逐渐放大的过程。
A 作为服务提供者,B 为 A 的服务消费者,c 和 D 是 B 的服务消费者,A 不可用引起了 B 的不可用,并将不可用像滚雪球一样放大到 C 和 D 时,雪崩效应就形成了。
3、解决雪崩效应的方式一是设置 "超时时间",二是使用断路器模式,而 Hystrix 库提供了超时机制与断路器模式。
超时机制
1、通过网络请求其他服务时,都必须设置超时。正常情况下,一个远程调用一般在几十毫秒内就返回了,当依赖的服务不可用,或者因为网络问题时,响应时间将会变得很长(几十秒 )。而通常情况下,一次远程调用对应了一个线程/进程,如果响应太慢,那这个线程/进程就会得不到释放,而线程/进程都对应了系统资源,如果大量的线程/进程得不到释放, 并且越积越多,服务资源就会被耗尽,从而导致资深服务不可用,所以必须为每个请求设置超时。
断路器模式
1、家里如果没有断路器,电流过载了(例如功率过大、短路等),电路不断开,电路就会升温,甚至是烧断电路而起火。有了断路器之后,当电流过载时,会自动切断电路(跳闸),从而保护了整条电路与家庭的安全。当电流过载的问题被解决后,只要关闭断路器,电路就又可以工作了。
2、同样的道理,当依赖的服务有大量超时时,再让新的请求去访问已经没有太大意义,只会无谓的消耗现有资源,假如我们设置了超时时间为 1 秒,如果短时间内有大量的请求(譬如50个)在1秒内都 得不到响应,就往往意味着异常。此时就没有必要让更多的请求去访问这个依赖了,我们应该使用断路器避免资源浪费。
3、断路器可以实现快速失败,如果它在一段时间内侦测到许多类似的错误(譬如超时),就会强迫其以后的多个调用快速失败,不再请求所依赖的服务,从而防止应用程序不断地尝试执行可能会失败 的操作,这样应用程序可以继续执行而不用等待修正错误,或者浪费 CPU 时间去等待长时间的超时。
4、断路器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。断路器模式就像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次 数,然后决定使用允许操作继续,或者立即返回错误。
Hystrix 断路器概述
1、Hystrix github 官网:https://github.com/Netflix/Hystrix
2、spring-cloud-netflix-Hystrix 官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.0.RELEASE/single/spring-cloud-netflix.html#_circuit_breaker_hystrix_clients
3、Netflix 创建了一个名为 Hystrix 的库,该库实现了断路器模式。
4、较低级别的服务中的服务故障可能会导致级联故障,直至用户。对特定服务的调用超过 circuitBreaker.requestVolumeThreshold(默认值:20个请求),并且故障百分比在 metrics.rollingStats.timeInMilliseconds (默认值:10秒)内大于 circuitBreaker.errorThresholdPercentage(默认值:> 50%),则断路器开启,无法继续请求,在错误和断路的情况下,开发人员可以提供后备功能。
开路可以停止级联故障,并让不堪重负的服务故障时间得以恢复。回退可以是另一个受 Hystrix 保护的调用,静态数据或合理的空值。可以将回退链接在一起,以便第一个回退进行其他业务调用,而后者又回退到静态数据。
Hystrix 断路器使用
1、使用非常简单,官网 How to Include Hystrix 介绍的很详细,分为3步:
1)pom.xml 文件导入依赖:
spring-cloud-starter-netflix-hystrix
2)启动类上添加注解表示启用断路器模式:
@EnableCircuitBreaker3)控制层方法上添加注解:@HystrixCommand(fallbackMethod = "defaultStores"),表示此方法往外抛出异常时,调用 defaultStores 方法
2、本文环境 java jdk 1.8 + Spring Boot 2.1.3 + Spring Cloud Greenwich.SR1 版本,演示的思路如下:
1)新建一个 Eureka Server 并启动,应用名叫 eureka_server2020
2)新建一个 Eureka Client 服务提供者客户端 ec-zebra 注册到 Eureka Server ,并对外提供服务
3)新建一个 Eureka Client 服务消费者客户端 snow-leopard 注册到 Eureka Server,并调用 ec-zebra 微服务。同时使用 hystrix 断路器。
4)先让 snow-leopard 正常调通 ec-zebra,然后手动关闭 ec-zebra 微服务,此时 snow-leopard 自动调用 fallbackMethod
5)最后再手动开启 ec-zebra,snow-leopard 便自动又开始正常调用 ec-zebra 了。
Eureka Server 注册中心
1、注册中心 pom.xml 依赖核心内容如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2、全局配置文件如下,使用单机模式进行演示:
server:
port: 8761
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 60000 #驱逐计时器扫描失效服务间隔时间。(单位毫秒,默认 60*1000)
instance:
hostname: localhost
client:
register-with-eureka: false #禁用自己向自己注册
fetch-registry: false #不同步其他的 Eureka Server节点的数据
service-url: #Eureka Client 与 Eureka Server 交互的地址
default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/
3、启动类上必须开启 Eureka Server:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @EnableEurekaServer :开启 eureka server ,否则 eureka 服务不会生效.
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer2020Application {
public static void main(String[] args) {
SpringApplication.run(EurekaServer2020Application.class, args);
}
}
服务提供者
1、pom.xml 依赖核心如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
2、全局配置文件内容如下:
server:
port: 9394 #服务器端口
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/ #eureka 服务器地址
instance:
prefer-ip-address: true # IP 地址代替主机名注册
instance-id: ${eureka.instance.appname}:${spring.cloud.client.ip-address}:${server.port} # 微服务节点实例名称
appname: ec-zebra #微服务名称
3、创建一个控制层,新建一个接口对外提供访问:
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PersonController {
/**
* 获取斑马信息
* http://localhost:9394/zebra/person/info?id=33980
*
* @param id
* @return
*/
@GetMapping("zebra/person/info")
public String info(String id) {
JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
ObjectNode objectNode = jsonNodeFactory.objectNode();
objectNode.put("code", 200);
objectNode.put("id", id);
objectNode.put("info", "斑马锦衣卫都指挥使");
return objectNode.toString();
}
}
服务消费者
1、pom.xml 在上面 "服务提供者" 的基础上加上断路器 spring-cloud-starter-netflix-hystrix 即可:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、全局配置文件内容如下:
server:
port: 9395 #服务器端口
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/ #eureka 服务器地址
instance:
prefer-ip-address: true # IP 地址代替主机名注册
instance-id: ${eureka.instance.appname}:${spring.cloud.client.ip-address}:${server.port} # 微服务节点实例名称
appname: snow-leopard #微服务名称
3、启动类上开启 hystrix 断路器(微服务之间的调用,为了简单,这里不使用 feign,直接使用 RestTemplate,加上 ribbon 组件时也是同理):
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @EnableCircuitBreaker :表示开启断路器模式,不写时只要导入了 hystrix 依赖默认也是开启的。可以不写。
* @EnableEurekaClient :只要导入了 eureka-client 依赖,则默认也是开启 eureka 客户端的。可以不写。
*/
@SpringBootApplication
@EnableCircuitBreaker
public class EcHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(EcHystrixApplication.class, args);
}
/**
* 微服务之间的调用,这里直接使用 org.springframework.web.client.RestTemplate 进行调用
* RestTemplate 它默认是没有交由 spring 容器管理的,需要自己添加到容器,然后才能使用。
*
* @return
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
4、新建一个控制层,并提供对外访问的接口,同时方法内部调用上面的服务提供者:
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class FoodController {
@Resource
private RestTemplate restTemplate;
private static JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
/**
* http://localhost:9395/leopard/food/foods?id=998760
* 1、当用户调用本接口时,如果本方法内部出现异常,且没有 try-catch 处理,则会默认自动倒退到 fallbackMethod 指定的方法
* 2、fallbackMethod 属性指定的回调方法的参数和返回值必须与本接口完全一致,否则抛异常.
* 3、比如 restTemplate.getForObject 超时或者连接拒绝,此时便会自动倒退到 foods_fallbackMethod 方法并返回给用户,且控制台也不会看到异常
* 4、再比如本接口内部故意写一个 int a = 1 / 0; 因为没有 try-catch 处理,默认也会自动倒退调用 foods_fallbackMethod 方法,且控制台不会打印异常信息
* 5、如果本接口内部使用 try{xxx} catch(Xxx e){xxx} 捕获了异常,则默认不再倒退到 foods_fallbackMethod 方法,而是接着继续往后走.
*
* @param id
* @return
*/
@GetMapping("leopard/food/foods")
@HystrixCommand(fallbackMethod = "foods_fallbackMethod")
public String foods(String id) {
ObjectNode objectNode = jsonNodeFactory.objectNode();
objectNode.put("code", 200);
objectNode.put("id", id);
objectNode.put("food", "红烧狮子头");
String url = "http://localhost:9394/zebra/person/info?id=33980";
ObjectNode responseEntity = restTemplate.getForObject(url, ObjectNode.class);
objectNode.set("info", responseEntity);
return objectNode.toString();
}
public String foods_fallbackMethod(String id) {
ObjectNode objectNode = jsonNodeFactory.objectNode();
objectNode.put("code", 200);
objectNode.put("id", id);
objectNode.put("food", "四喜丸子");
objectNode.set("info", null);
return objectNode.toString();
}
}
断路器测试
1)先启动 注册中心,以及微服务:
2、浏览器访问 snow-leopard 服务消费者,它内部调用 EC-ZEBRA服务提供者,正常级联调用之后,手动关闭 EC-ZEBRA,接着再访问 snow-leopard 时,可以看到他已经倒退调用 fallback 方法了,最后重新启动 EC-ZEBRA,服务级联调用便又成功了。
3、下面是拓展,表明 @HystrixCommand 标识的接口如果对外抛出异常,则它默认调用 fallback,而如果接口内部的异常手动捕获处理了,则默认不再倒退,而是继续执行本接口。
hystrix Health Indicator 健康指标
1、官网 Health Indicator 中介绍 "连接的断路器的状态也显示在调用应用程序的 /health 端点中"。
"hystrix": {
"status": "CIRCUIT_OPEN",
"details": {
"openCircuitBreakers": [
"FoodController::foods"
]
}
}
2、意思很简单就是 Spring Boot Actuator 监控和管理应用程序 中的 /health 健康信息端点中含有 hystrix 断路器信息,可以看到当前断路器的开关状态,具体到哪些接口已经开启断路器。
3、首先在需要监控的微服务中添加 actuator 依赖 :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4、/health 端点默认是不展示细节给未经验证授权的用户的,所以可以手动配置它开启:
#配置 actuator
management:
endpoint:
health:
show-details: always #将健康信息的细节展示给所有用户查看,默认为 never(不展示细节给所有用户)
5、最后便可以进行验证了,访问地址:http://ip:port/content-path/actuator/health,可以看到 hystrix 的信息。注意上面已经说过:当超过 20 个请求,统计故障百分比 10 秒内 > 50% 时,打开断路器。
CIRCUIT_OPEN:表示断路器打开。