熔断、限流、降级 —— SpringCloud Hystrix

概述

Hystrix 为 微服务架构提供了一整套服务隔离、服务熔断和服务降级的解决方案。它是熔断器的一种实现,主要用于解决微服务架构的高可用及服务雪崩等问题

Hystrix 的特性如下:

  • 服务熔断:Hystrix 熔断器就像家中的安全阀一样,一旦某个服务不可用,熔断器就会直接切断该链路上的请求,避免大量的无效请求影响系统稳定,并且熔断器有自我检测和恢复的功能,在服务状态恢复正常后会自动关闭
  • 服务降级:Hystrix 通过 falback 实现服务降级,在需要进行服务降级的类中定义一个 falback 方法,当请求的远程服务出现异常时,可以直接使用 fallback 方法返回异常信息,而不调用远程服务
  • 依赖隔离:Hyslrix 通过线程池和信号量两种方式实现服务之间的依赖隔离,这样即使其中一个服务出现异常,资源迟迟不能释放,也不会影响其他业务线程的正常运行
    • 线程池的隔离策略:Hystrix 线程池的资源隔离为每个依赖的服务都分配一个线程池,每个线程池都处理特定的服务,多个服务之间的线程资源互不影响,以达到资源隔离的目标。当某个依赖服务异常时,只会阻塞这个依赖服务的线程资源,不影响其他依赖服务
    • 信号量的隔离策略:Hystrix 信号量的隔离策略是为每个依赣的服务都分配一个信号量(原子计数器),当请求某个依赖服务时,先判断该服务的信号量值是否超过最大值。如果超过,则直接丢弃并返回错误提示,如果不超过,则在处理请求前执行“信号量+1”的操作,在请求返回后执行“信号量-1”的操作
  • 请求缓存:Hystrix 按照请求参数把请求结果缓存起来,当后面有相同的请求时不会再走完整的调用链流程,而是把上次缓存的结果直接返回,以达到服务快速响应和性能优化的目的。同时,缓存可作为服务降级的数据源,当远程服务不可用时,直接返回缓存数据,对于消费者来说,只是可能获取了过期的数据,这样就优雅地处理了系统异常
  • 请求合井:当微服务需要调用多个远程服务做结果的汇总时,需要使用请求合并。Hystrix 采用异步消息订阅的方式进行请求合并,当应用程序需要请求多个接口时,采用异步调用的方式提交请求,然后订阅返回值,应用程序的业务可以接着执行其他任务而不用阻塞等待,当所有请求都返回时,应用程序会得到一个通知,取出返回值合并即可

Hystrix 服务降级流程

  1. 当有服务请求时,首先会根据注解创建一个 HystrixCommand 指令对象,该对象设置了服务调用失败的场景和调用失败后服务降级的业务逻辑方法
  2. 熔斯器判断状态,当熔断器处于开路状态时,直接调用服务降级的业务逻辑方法返回调用失败的反馈信息
  3. 当熔断器处于半开路或者闭路状态时,服务会进行线程池和信号量等检查如果有可用资源,有则调用正常业务逻辑。如果调用正常业务逻辑成功,则返回成功后的息,如果失败,则调用降级的业务逻辑,进行服务降级
  4. 当熔断器处于半开路或者闭路状态时,如果在当前服务线程池和信号量中无可用资源,则执行服务降级的业务逻辑,返回调用失败的信息
  5. 当熔断器处于半开路状态并且本次服务执行失败时,熔断器会进入开路状态
  6. 当正常业务逻辑处理超时或者出现错误时,HystrixCommand 会执行服务降缓的业务逐辑,返回调用失败的信息
  7. 线程池和信号量的资源检查及正常业务逻辑会将自己的状态和调用结果反馈给监控,监控将服务状态反馈给熔断器,以便熔断器判断熔断状态

Hystrix 应用

Hystrix 的使用主要分为服务熔断、服务降级和服务监控三个方面

在 pom.xml 文件中引入 Hystrix 依赖,其中,spring-cloud-slarter-netflix-hystrix 和 hystrix-javanica 为 Hystrix 服务熔断所需的依赖,spring-cloud-netflix-hystrix-dashboard 为 Hystrix 服务监控所需的依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-eureka</artifactId>
  <version>1.4.6.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  <version>1.4.6.RELEASE</version>
</dependency>
<dependency>
  <groupId>com.netflix.hystrix</groupId>
  <artifactId>hystrix-javanica</artifactId>
  <version>RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
  <version>1.4.6.RELEASE</version>
</dependency>

通过 @EnableHystrix 注解开启对服务熔断的支持,通过 @EnableHystrixDashboard 注解开启对服务监控的支持。注意,Hystrix 一般和服务发现配合使用,这里使 @EnableEurekaClient 开启了对服务发现客户端的支持

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@EnableHystrixDashboard
public class HystrixServiceApplication {

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

  @Bean
  public IRule ribbonRule() {
    return new RandomRule();
  }

  @Bean
  @LoadBalanced
  public RestTemplate restTemplate() {
    return new RestTemplate();
  }
}

配置 application.properties 文件

#服务名
spring.application.name=hystrix
#服务的端口
server.port=9005
#注册中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:9001/eureka/
eureka.client.registry-fetch-interval-seconds=30

服务熔断和降级,定义了一个远程调用的方法 hystrixHandler(),并通过 @HystrixCommand(fallbackMethod="exceptionHandler") 在方法上定义了一个服务降级的命令,当远程方法调用失败时,Hystrix 会自动调用 fallbackMethod 来完成服务熔断和降级,这里会调用 exceptionHandler 方法

@Autowired
private RestTemplate restTemplate;

//定义服务降级命令
@HystrixCommand(fallbackMethod = "exceptionHandler")
@RequestMapping(value = "/service/hystrix", method = RequestMethod.GET)
public String hystrixHandler() {
  return restTemplate.getForEntity("http://EUREKA-CLIENT/serviceProducer", String.class).getBody();
}

public String exceptionHandler() {
  return "提供者服务挂了";
}

异步处理

上节中的远程调用请求必须等到网络请求返回结果后,才会执行后面的代码,即阻塞运行。而在实际使用过程中,应用程序常常希望使用非阻塞 IO 来更优雅地实现功能.Hyslrix 为非阻塞 IO 提供了两种实现方式,分别是表示将来式的 Future 和表示回调式的 Callable

1. Future

定义 HystrixCommand

public class CommandFuture extends HystrixCommand<String> {

  private String name;

  private RestTemplate restTemplate;

  public CommandFuture(String name, RestTemplate restTemplate) {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                //1:通过 HystrixCommandKey 工厂定义依赖的名称
                .andCommandKey(HystrixComnandKey.Factory.asKey("HelloWorld"))
                //2:通过 HystrixThreadPoolKey 工厂定义线程池的名称
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));
    this.name = name;
    this.restTemplate = restTemplate;
  }

  //3:定义远程调用的方法体
  @Override
  protected String run() {
    String result = restTemplate.getForEntity("http://EUREKA-CLIENT/serviceProducer", String.class).getBody();
    return result;
  }

  //4:服务降级的处理逻辑
  @Override
  protected String getFallback() {
    return "远程服务异常";
  }
}

以上代码通过继承 HystrixCommand 定义了一个 CommandFuture 来实现异步请求,其中,正常业务执行的逻辑在覆写的 run() 方法体中被执行,服务降级的方法在 getFallback() 中被执行。需要注意的是,这里使用 andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")) 实现了使用 HystrixCommandKey 工厂定义依赖的名称,每个 CommandKey 都代表一个依赖抽象,相同的依赖要使用相同的 CommandKey 名称。依赖隔离的本质敦是对相同
CommandKey 的依赖进行离,使用 andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")) 实现了基于 HystrixThreadPoolKey 工厂定义线程池的名称。当对同一业务的依赖进行资源隔离时,使用 CommandGroup 进行区分,但是当对同一依赖进行不同的远程调用时(例如,一个是 Redis 服务,一个是 HTTP 服务),则可以使用 HystrixThreadPoolKey 进行隔离区分

使用 HystrixCommand

@RequeatMapping(value = "/service/hystrix/future", method = RequestMethod.GET)
public String hystrixFutureHandler() throws ExecutionException, InterruptedException {
  //定义基于Future的异步调用,请求会以队列的形式在线程池中被执行
  Future<String> future = new CommandFuture("future", restTemplate).queue();
  return future.get();
}

2. Callable

预定义一个回调任务,Callable 在发出请求后,主线程继续执行,在请求执行完成并返回结果后,Callable 会自动调用回调任务

定义 HystrixObservableCommand

public class CommandObservable extends HystrixObservabCommand<String> {

  private String name;
  private RestTemplate restTemplate;

  public CommandObservable(String nane, RestTemplate restTemplate) {
    
    super(HystrixConmandGroupKey.Factory.asKey("ExampleGroup"));
    this.nane = name;
    this.restTemplate = restTemplate;
  }

  //基于观察者模式的请求发布
  @Override
  protected Observable<String> construct () {
    return Observable.create(new Observable.OnSubscribe<String>() {
      @Override
      public void call(Subscriber<? super String> subscriber) {
        try {
          //执行远程过程调用
          if(!subscriber.isUnsubscribed()) {
            String result = restTemplate.getForEntity("http://EUREKA-CLIENT/serviceProducer", String.class).getBody();
            //将调用结果传递下去
            subscriber.onNext(result):
            subscriber.onCompleted();
          }
        } catch(Exception e) {
          e.printStackTrace();
          subscriber.onError(e);
        }
      }
    }
  } 

  //服务降级的处理逻辑
  @Override
  protected Observable<String> resumeWithFallback() {
    return Observable.create(new Observable.OnSubscribe<String>() {
      @Override
      public void call(Subscriber<? super String> subscriber) {
        try {
          if(!subscriber.isUnsubscribed()) {
            subscriber.onNext("远程服务异常”);
            subscriber.onCompleted();
        } catch (Exception e) {
            subscriber.onError(e);        
        }
      }
    }
  }
}

以上代码定义了名为 CommandObservable 的类,该类继承自 HystrixObservableCommand 接口,并通过覆写 HystrixObservableCommand 接口中的 construct() 实现观察者模式。具体实现为通过 Obsenvable.create() 创建并返回一个 Observable 对象,在创建对象时,通过 new Observable.OnSubscribe() 实现消息的监听和处理。其中,call 方法用于消息的接收和业务的处理,在消息处理完成后通过 subscriber.onNext(result) 将调用结果传递下去,当所有任务都执行完成时通过 subscriber.onCompleted() 将总体执行结果发布出去。resumeWithFallback 方法是服务降级的处理逻辑,当服务出现异常时,通过 subscriber.onNext("远程服务异常") 进行服务熔断和异常消息的发布,实现服务降级处理

使用 HystrixObservableCommand

public String hystrixCallableHandler() throws ExecutionException, InterruptedException {

  List<String> list = new ArrayList<>();

  //定义基于消息订间的异步调用,请求结果会以事件的方式通知
  Observable<String> observable = new CommandObservable("observer", restTemplate).observe();
  //基于观察者模式的清求结果订阅
  observable.subscribe(new Observer<String>() {
    //onCompleted方法在所有请求完成后执行@
    @Override
    public void onCompleted() {
      System.out.println("所有请求已经完成...”);
    }
    @Override
    public void onError(Throwable throwable) {
      throwable.printStackTrace();
    }
    //订阅调用事件,请求结果汇聚的地方,用集合将返回的结果收集起来
    @Override
    public void onNext(String s) {
      System.out.printin("结果来了...");
      list.add(s);
      return list.toString();
    }
  }
}

以上代码通过 new CommandObservable("observer", restTemplate).observe() 定义了一个实现服务发布的命令。通过调用 abservabe.subscribe() 来实现基于观察者模式的请求结果订阅,其中,订阅的数据结果在 onNext() 中被通知,总体调用结果在 onCompleted() 中被通知。服务处理异常结果在 onError() 中被通知


Hystrix Dashboard

HystrixDashboard 主要用于实时监控 Hystrix 的各项运行指标。通过 HystrixDashboard 可以查询 Hystrix 的实时信息,用于快速定位和发现问题。Hystrix Dashboard 的使用简单、方便,首先在 pom.xml 文件中加入 spring-cloud-netfix-hystrix-dashboard 依赖,然后使用 @EnableHystrixDashboard 注解开启 Dashboard 功能即可。在服务启动后,在浏览器地址栏中输人 http://127.0.0.1:9005/hystrix,就可以看到监控界面