一、容错
1、雪崩效应
2、容错手段
1.为网络请求设置超时
2.使用断路器模式
二、Hystrix实现容错
1、整合Hystrix
2、HyStrix的状态监控与深入解析
3、隔离策略
如果服务提供者相应非常缓慢,那么消费者对提供者的请求就会被强制等待,知道提供者相应超时。在高负载场景下,如果不作任何处理,此类问题可能会导致服务消费者的资源耗尽甚至整个系统崩溃。
微服务架构的应用系统通常包含多个服务层,微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,微服务之间难免存在依赖关系。我们常把“基础服务故障”导致“级联故障”的现象成为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐步扩大的过程。
如下图所示:
A\B作为微服务消费者分别消费微服务C\D,微服务C\D同时作为微服务消费者消费微服务E。
当微服务E出现故障。
由于微服务E不可用,微服务C\D调用失败,请求堆积,占用系统资源,最后导致微服务C\D不可用。
由于微服务C\D不可用,微服务A\B调用失败,请求堆积,占用系统资源,最后导致微服务A\B也不可用。
最终的结果就是由于微服务E出现故障已至整个微服务集群瘫痪掉。
必须为网络请求设置超时。正常情况下,一个远程调用一般在几十毫秒内就能得到响应了。如果依赖的服务不可用或者网络有问题,那么响应时间就会变得特别长。
通常情况下,一次远程调用对应着一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。而线程/进程又对应着系统资源,如果得不到释放的线程/进程约积越多,资源就会逐渐被耗尽,最终导致服务的不可用。
如果对某个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该服务已经没有任何意义,只会无所谓消耗资源。例如,设置了超时时间为1秒,如果短时间内有大量的请求无法在1秒内得到响应,就没有必要再去请求依赖的服务了。
短路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(例如超时),就会在之后的一段时间内,强迫对该服务的调用快速失败,即不再请求所依赖的服务。这样,应用程序就无需再浪费cpu时间去等待长时间的超时。
短路器也可自动诊断是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复”——当依赖的服务不正常打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。
断路器状态转换逻辑如上图所示:
- 正常情况下,断路器关闭,可正常请求依赖的服务
- 当一段时间内,请求失败率达到一定阀值(例如错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。
- 断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。
Hystrix是一个实现了超时机制和断路器模式的工具类库。是由Netfix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。
1.复制项目eureka-client-consumer-ribbon为eureka-client-consumer-hystrix
2.添加Hystrix依赖
完整pom.xml如下:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
com.springclouddemo
eureka-client-consumer-hystrix
0.0.1-SNAPSHOT
eureka-client-consumer-hystrix
整合Hystrix
1.8
Greenwich.SR1
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
3.为main类添加@EnableHystrix启动熔断器
package com.springclouddemo.eurekaclientconsumerhystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @author 何昌杰
*/
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class EurekaClientConsumerHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientConsumerHystrixApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
4.修改application配置文件
spring.application.name=eureka-client-consumer-hystrix
server.port=7206
eureka.client.service-url.defaultZone=http://localhost:7000/eureka/
eureka.instance.prefer-ip-address=true
5.将DemoController.java修改如下
package com.springclouddemo.eurekaclientconsumerhystrix.controllers;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.ribbon.proxy.annotation.Hystrix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author 何昌杰
*/
@RestController
public class DemoController {
private static final Logger log= LoggerFactory.getLogger(DemoController.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/helloribbon")
public void ribbonDemo(){
ServiceInstance choose = this.loadBalancerClient.choose("eureka-client-provider");
log.info(choose.getInstanceId()+","+choose.getHost()+":"+choose.getPort());
}
@GetMapping("/hello/{name}")
@HystrixCommand(fallbackMethod = "demo2Fallback")
public String demo1(@PathVariable("name") String name){
return this.restTemplate.getForObject("http://localhost:7100/hello/"+name,String.class);
}
public String demo2Fallback(String name){
return "模拟默认请求响应";
}
}
断路器的状态监控需要通过Actuator实现
org.springframework.boot
spring-boot-starter-actuator
断路器的状态会暴露在Actuator提供的/health端点中,这样就可以直观了解断路器的状态。
测试:
这里我们发现虽然执行的回退方案返回了默认值,但是Hystrix的状态依然是UP,这是因为失败请求还未达到阈值(默认是5秒20次失败)
{
"status":"UP",
"hystrix":{
"status":"UP"
}
...
}
模拟默认请求响应
{
"status":"UP",
"hystrix":{
"status":"UP"
}
...
}
{
"status":"UP",
"hystrix":{
"status":"CIRCUIT_OPEN",
"openCircuitBreakers":[
"DemoController::demo1"
]
}
...
}
Hystrix的状态为CIRCUIT_OPEN说明断路器已经打开,不会去调用其他微服务了。
Hystrix的隔离策略有两种:线程隔离和信号量隔离。
Hystrix中默认使用线程隔离(THREAD),因为这种方式有一个除网络超时以外的额外保护层。
一般来说,只有当调用负债非常高时(至少每秒数百次)才需要使用信号量隔离,因为这种场景使用线程隔离开销会比较高,信号量格式一般仅适用于非网络调用的隔离。
可以使用execution.isolation.strategy属性指定隔离策略。
@GetMapping("/hello/{name}")
@HystrixCommand(fallbackMethod = "demo2Fallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE")
}
)
public String demo1(@PathVariable("name") String name){
return this.restTemplate.getForObject("http://localhost:7100/hello/"+name,String.class);
}
更多关于Hystrix的内容各位可移步至此:Hystrix详解(如何保护、限流、熔断、隔离、降级、源码分析)
1.复制项目eureka-client-consumer-feign为eureka-client-consumer-feign-fallback
2、将ProviderFeign.java修改如下:
package com.springclouddemo.eurekaclientconsumerfeignfallback.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author 何昌杰
*/
@Component
@FeignClient(name = "eureka-client-provider",fallback = ProviderFeignFallback.class)
public interface ProviderFeign {
@GetMapping("/hello/{name}")
String demo1(@PathVariable("name") String name);
}
class ProviderFeignFallback implements ProviderFeign{
@Override
public String demo1(String name) {
return "默认返回值";
}
}
通过上一小节我们了解到了如何在Feign中添加回退,但是当执行回退方法时,我们无从得知为何进行了回退。本小节为例讲述为Feign添加回退日志。
1.复制项目eureka-client-consumer-feign为eureka-client-consumer-feign-fallback-factory
2.将ProviderFeign.java修改如下
package com.springclouddemo.eurekaclientconsumerfeignfallbackfactory.feign;
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author 何昌杰
*/
@Component
@FeignClient(name = "eureka-client-provider",fallbackFactory = FeignClientFallbackFactory.class)
public interface ProviderFeign {
@GetMapping("/hello/{name}")
String demo1(@PathVariable("name") String name);
}
@Component
class FeignClientFallbackFactory implements FallbackFactory{
private static final Logger log = LoggerFactory.getLogger(FeignClientFallbackFactory.class);
@Override
public ProviderFeign create(Throwable throwable) {
return new ProviderFeign() {
/**
* 将日志置于fallback方法中防止引用启动时输出日志
* @param name 请求参数
* @return 响应
*/
@Override
public String demo1(String name) {
log.info("fallback; the reason was:",throwable);
return "默认返回值";
}
};
}
}
3.编写application配置文件
spring.application.name=eureka-client-consumer-feign-fallback-factory
server.port=7208
eureka.client.service-url.defaultZone=http://localhost:7000/eureka/
eureka.instance.prefer-ip-address=true
feign.hystrix.enabled=true
测试:
2019-07-09 12:21:03.991 INFO 9076 --- [ HystrixTimer-1] c.s.e.feign.FeignClientFallbackFactory : fallback; the reason was:Load balance does not have available server for client: eureka-client-provider
控制台输出如下:
2019-07-09 12:21:03.991 INFO 9076 --- [ HystrixTimer-1] c.s.e.feign.FeignClientFallbackFactory : fallback; the reason was:Load balance does not have available server for client: eureka-client-provider
说明进入了回退方法。
@Component
class FeignClientFallbackFactory implements FallbackFactory{
private static final Logger log = LoggerFactory.getLogger(FeignClientFallbackFactory.class);
@Override
public ProviderFeign create(Throwable throwable) {
return new ProviderFeign() {
/**
* 将日志置于fallback方法中防止引用启动时输出日志
* @param name 请求参数
* @return 响应
*/
@Override
public String demo1(String name) {
if(throwable instanceof IllegalAccessException){
//..
}else if(throwable instanceof ClassCastException){
//...
}
return "默认返回值";
}
};
}
}
使用Feign自定义配置可以方便的禁用Hystrix,禁用Hystrix可分为两种方式,一种禁用指定Feign接口,一种是全局禁用Feign
创建配置类
@Configuration
public class FeignDisableHystrixConfiguration{
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
为需要禁用Hystrix的FeignClient引用该配置
@Component
@FeignClient(name = "eureka-client-provider", configuration = FeignDisableHystrixConfiguration.class)
public interface ProviderFeign {
//...
}
在application中添加配置项:
feign.hystrix.enabled=false