• Hystrix 详细说明


    在 Hystrix 入门中,使用 Hystrix 时创建命令并给予执行,实际上 Hystrix 有一套较为复杂的执行逻辑,简单来说明以下运作流程:

    • 在命令开始执行时,会做一些准备工作,例如为命令创建响应的线程池等
    • 判断是否打开了缓存,打开了缓存就直接查找缓存并返回结果。
    • 判断断路器是否打开,如果打开了,就表示服务链路不可用,直接执行回退方法。
    • 判断线程池、信号量(计数器)等条件,例如,线程池超负荷,则执行回退方法
    • 执行命令,计算是否要对断路器进行处理,执行完成后,如果满足一定条件,则需要开启断路器。如果执行成功则返回结果,执行失败则执行回退

       

    Hystrix 官方的执行流程图如下:

       

    命令执行

    Hystrix 命令执行可以使用以下方法来执行命令:

    • toObservable:返回一个最原始的 Observable 实例,Observable 是 RxJava 的类,使用该对象可以观察命令的执行过程,并且将执行信息传递给订阅者,该方法是异步执行
    • observe:调用 toObservable 方法,获取一个原始的 Observable 实例,使用 ReplaySubject 作为原始 Observable 的订阅者,该方法是异步执行
    • queue:通过 toObservable 方法获取原始的 Observable 实例,在调用 Observable 的 toBlocking 方法得到一个 BlockingObservable 实例,最后调用 BlockingObservable 的 toFuture 方法返回 Future 实例,最后调用 Future 实例的 get 方法得到执行结果,该方法是异步执行
    • execute:该方法调用 queue 的 get 方法返回命令的执行结果,该方法时同步执行

    除了 execute 方法外,其他的方法都是异步执行,observe 与 toObservable 方法的区别是,toObservable 调用后,不会立即执行,只有当返回的 Observable 实例被订阅后,才会真正的执行命令,并且只能一次订阅,而 observe 方法返回的 Observable 可以支持多次订阅;每个命令实例对象,只能执行一次。四种命令的执行示例如下:

    package org.lixue.hystrixclient;

       

    import rx.Observable;

    import rx.Observer;

    import rx.functions.Action0;

    import rx.functions.Action1;

       

    import java.util.concurrent.Future;

       

    public class HystrixClient{

    publicstaticvoidmain(String[]args){

    SpeakSleepCommand cmd=null;

    try{

    //调用execute方法

    cmd=new SpeakSleepCommand(0);

    String result=cmd.execute();

    System.out.println("execute请求结果="+result);

       

    //调用queue方法

    cmd=new SpeakSleepCommand(0);

    Future<String>future=cmd.queue();

    result=future.get();

    System.out.println("queue 请求结果="+result);

       

    //调用observe方法

    cmd=new SpeakSleepCommand(0);

    Observable<String> observable=cmd.observe();

    observable.subscribe(new Observer<String>(){

    public void onCompleted(){

    System.out.println("subscribe onCompleted 请求完成");

    }

       

    public void onError(Throwable throwable){

    System.out.println("subscribe onError 请求结果错误="+throwable);

    }

       

    public void onNext(String s){

    System.out.println("subscribe onNext 请求结果="+s);

    }

    });

    observable.subscribe(new Action1<String>(){

    public void call(Strings){

    System.out.println("subscribe onNext call 请求结果="+s);

    }

    });

       

    //调用toObservable方法

    cmd=new SpeakSleepCommand(0);

    Observable<String>toObservable=cmd.toObservable();

    toObservable.subscribe(newObserver<String>(){

    public void onCompleted(){

    System.out.println("toObservable subscribe onCompleted 请求完成");

    }

       

    public void onError(Throwablet hrowable){

    System.out.println("toObservable subscribe onError 请求失败");

    }

       

    public void onNext(String s){

    System.out.println("toObservable subscribe onNext 请求结果="+s);

    }

    });

    //因为是异步的,因此必须线程等待,否则会导致退出,无法获取toObservable的订阅结果

    Thread.sleep(100);

    }catch(Exceptionex){

    ex.printStackTrace();

    }

    }

    }

       

    回退

    根据 Hystrix 的执行流程图可以发现,有三种情况下会触发回退(fallback)操作:

    • 断路器被打开
    • 线程池、队列、信号量满载
    • 实际执行命令失败

    Hystrix 的回退机制比较灵活,可以在 A 命令的回退方法中执行 B 命令,如果 B 命令执行失败则会触发 B 命令的回退,这样就会形成一种链式的命令执行。

    还有一种情况,在 A 命令中调用 B 命令和C 命令,如果 B 命令或者 C 命令调用失败,并且没有提供回退方法,则会调用 A 命令的回退方法,如果 B 命令或 C 明天提供了回退方法,则不会调用 A 命令的回退。多命令调用回退如下图所示:

       

       

       

       

    断路器开启/关闭

    断路器一旦开启,就会直接调用回退方法,不在执行命令,而且也不会更新链路的健康状况,断路器的开启要满足两个条件:

    • 整个链路达到一定的阈值,默认情况下,10秒内产生超过20次请求,则符合第一个条件
    • 满足第一个条件的情况下,如果请求的错误百分比大于阈值,则会打开断路器,默认为 50%

    断路器开启相关配置:

    • hystrix.command.default.metrics.rollingStats.timeInMilliseconds:默认值 10000,滚动窗口的时间范围
    • hystrix.command.default.circuitBreaker.requestVolumeThreshold:默认值 20,滚动窗口期间的最小请求数
    • hystrix.command.default.circuitBreaker.errorThresholdPercentage:默认值 50,滚动窗口期间的调用失败比例,高出该比例开启断路器

    断路器打开后,在一段时间内,命令不会再执行,这段时间称为休眠期,休眠期默认是5秒,休眠期结束后,Hystrix 会尝试性的执行一次命令,此时断路器的状态不是开启,也不是关闭,而是一个半开的状态,如果这一次命令执行成功,则会关闭断路器并清空链路的健康信息;如果执行失败,断路器会继续保持打开的状态。

    断路器关闭相关配置:

    • hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds:默认值 5000,休眠期的持续时间

         

    隔离机制

    命令的真正执行,除了断路器要关闭外,还需要看执行命令的线程池或者信号量是否满载,如果满载,命令就不会执行,而是直接触发回退,这样的机制,在控制命令的执行上,实现了错误的隔离,Hystrix 提供了两种隔离策略:

    • THREAD:默认值,由线程池来决定命令的执行,如果线程池满载,则不会执行命令。线程池默认大小为 10
    • SEMAPHORE:由信号量来决定命令执行,当请求的并发数高于阈值时,就不再执行命令,相对与线程池,信号量的开销更小,但是该策略不支持超时以及异步。

       

    合并请求

    默认情况下,会为命令分配线程池来执行命令实例,线程池会消耗一定的性能,对于一些同类型的请求(URL相同,参数不同),Hystrix 提供了合并请求的功能,在一次请求的过程中,可以将一个时间段内的相同请求(参数不同),收集到一个命令中执行,这样节省了线程的开销,减少网络连接,从而提升了执行的性能,实现合并请求的功能,至少包含以下三个条件:

    • 需要一个执行请求的命令,将全部参数进行整理,然后调用外部服务
    • 需要一个合并处理器,用于收集请求,以及结果处理
    • 外部接口提供支持,需要能支持批量处理的功能

    合并请求只执行了一个命令,只启动了一个线程,只进行了一次网络请求,但是在收集请求、合并请求、处理结果的过程中仍然会耗费一定的时间,一般情况下,合并请求进行批量处理,比发送多个请求快。

    合并请求示例:

    在Hystrix 入门的示例中进行修改,增加如下代码:

    • 创建执行请求的命令

      主要用于处理合并后的具体处理命令,组合多个请求的参数,向批量处理接口提交。

      package org.lixue.hystrixclient;

         

      import com.alibaba.fastjson.JSONObject;

      import com.netflix.hystrix.*;

      import org.apache.http.HttpResponse;

      import org.apache.http.client.methods.HttpGet;

      import org.apache.http.impl.client.CloseableHttpClient;

      import org.apache.http.impl.client.HttpClients;

      import org.apache.http.util.EntityUtils;

         

      import java.util.Collection;

      import java.util.LinkedList;

      import java.util.List;

      import java.util.Map;

         

      public class SpeakCommand extends HystrixCommand<Map<String,String>>{

      //CollapsedRequest泛型的第一个参数为返回对象类型,第二个参数为请求对象类型

      Collection<HystrixCollapser.CollapsedRequest<String,String>> collapsedRequests;

      private CloseableHttpClient httpClient;

      private Strin gurl;

         

      public SpeakCommand(Collection<HystrixCollapser.CollapsedRequest<String,String>> collapsedRequests){

      super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("BatchSpeak")));

      this.collapsedRequests=collapsedRequests;

      this.httpClient=HttpClients.createDefault();

      this.url="http://localhost:8080/speaks?names=";

      }

         

      protected Map<String,String> run() throws Exception{

      StringBuilde rstringBuilder=new StringBuilder();

      for(HystrixCollapser.CollapsedRequest<String,String> entry:collapsedRequests){

      if(stringBuilder.length()>0){

      stringBuilder.append(",");

      }

      stringBuilder.append(entry.getArgument());

      }

         

      try{

      HttpGet request=new HttpGet(this.url+stringBuilder.toString());

      HttpResponse response=httpClient.execute(request);

      String responseJson=EntityUtils.toString(response.getEntity());

      Map<String,String> result=(Map<String,String>)JSONObject.parse(responseJson);

      return result;

      }catch(Exceptionex){

      ex.printStackTrace();

      returnnull;

      }

      }

      }

         

    • 创建合并处理器

      合并处理器,用于处理请求的合并和结果的映射,而具体的请求是调用请求命令来完成的。

      package org.lixue.hystrixclient;

         

      import com.netflix.hystrix.HystrixCollapser;

      import com.netflix.hystrix.HystrixCollapserKey;

      import com.netflix.hystrix.HystrixCollapserProperties;

      import com.netflix.hystrix.HystrixCommand;

         

      import java.util.Collection;

      import java.util.Map;

      //HystrixCollapser类型泛型参数,Map<String,String>命令返回类型;String响应返回对象类型;String请求参数对象类型

      public class SpeakCollapserCommand extends HystrixCollapser<Map<String,String>,String,String>{

         

      String paramName;

         

      public SpeakCollapserCommand(StringparamName){

      super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("SpeakCollapser"))

      //设置合并请求的时间,在1秒内的请求进行合并

      .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(1000)));

      this.paramName=paramName;

      }

      /**

      * 返回请求参数

      *

      */

      public String getRequestArgument(){

      return this.paramName;

      }

      /**

      * 创建具体请求命令

      *

      *@param collapsedRequests

      *@return

      */

      protected HystrixCommand<Map<String,String>> createCommand(Collection<CollapsedRequest<String,String>> collapsedRequests){

      return new SpeakCommand(collapsedRequests);

      }

      /**

      * 处理返回结果映射到请求

      *

      *@param batchResponse

      *@paramc ollapsedRequests

      */

      protected void mapResponseToRequests(Map<String,String>batchResponse,Collection<CollapsedRequest<String,String>>collapsedRequests){

      for(CollapsedRequest<String,String>entry:collapsedRequests){

      String result=batchResponse.get(entry.getArgument());

      entry.setResponse(result);

      }

      }

      }

    • 创建服务代码

      在 server-provider(该项目提供 REST 服务) 增加批量处理 REST 服务,代码如下:

      package org.lixue.webservices.services;

         

      import org.springframework.beans.factory.annotation.Value;

      import org.springframework.http.MediaType;

      import org.springframework.web.bind.annotation.*;

         

      import java.util.Date;

      import java.util.HashMap;

      import java.util.HashSet;

      import java.util.Map;

         

      @RestController

      public class HelloWorldController{

         

      @Value("${server.port}")

      private int port;

         

      @RequestMapping(method=RequestMethod.GET,name="speak",path="/speaks",

      produces=MediaType.APPLICATION_JSON_UTF8_VALUE)

      public Map<String,String> speaks(@RequestParam(value="names")String names) throws InterruptedException {

      Map<String,String> map=new HashMap<>();

      if(names==null || "".equals(names)){

      return map;

      }

         

      String[]splitName=names.split(",");

      for(String name:splitName){

      map.put(name,"HelloWorld"+name+"Port="+port);

      }

         

      return map;

      }

      }

    • 创建启动类

      需要在初始化的时候,开启 HystrixRequestContext 否则无法支持合并请求

      package org.lixue.hystrixclient;

         

      import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

      import rx.Observable;

      import rx.Observer;

      import rx.functions.Action1;

         

      import java.util.concurrent.Future;

         

      public class HystrixClient{

      public static void main(String[]args){

      //需要开启HystrixRequest上下文,合并请求和缓存必须开启

      HystrixRequestContext context= HystrixRequestContext.initializeContext();

      try{

      Future<String> collapser01=new SpeakCollapserCommand("001").queue();

      Future<String> collapser02=new SpeakCollapserCommand("002").queue();

      Future<String> collapser03=new SpeakCollapserCommand("003").queue();

      Future<String> collapser04=new SpeakCollapserCommand("004").queue();

      System.out.println(collapser01.get());

      System.out.println(collapser02.get());

      System.out.println(collapser03.get());

      System.out.println(collapser04.get());

      }catch(Exceptionex){

      ex.printStackTrace();

      }

      finally{

      context.shutdown();

      }

      }

      }

         

    • 测试验证

      首先启动 server-provider 项目,然后启动该项目,由于我们设置的是 1 秒内的请求进行合并,因此可以看到会延迟一秒然后返回结果,可以在服务器端增加日志,看到只接收到了一次请求。

    请求缓存

    Hystrix 支持缓存功能,如果在一次请求的过程中,多个地方调用同一个接口,可以考虑使用缓存,缓存打开后,下一次命令不会执行,直接重缓存中获取响应并返回,开启缓存比较简单,在命令中重写父类的 getCacheKey 方法即可。

       

    缓存示例:

    • 创建命令

      重写了 getCacheKey 方法,如果返回 null 表示不启用缓存,我们使用请求参数来做为缓存key

      package org.lixue.hystrixclient;

         

      import com.netflix.hystrix.HystrixCommand;

      import com.netflix.hystrix.HystrixCommandGroupKey;

      import org.apache.http.HttpResponse;

      import org.apache.http.client.methods.HttpGet;

      import org.apache.http.impl.client.CloseableHttpClient;

      import org.apache.http.impl.client.HttpClients;

      import org.apache.http.util.EntityUtils;

         

      public class SpeakCacheCommand extends HystrixCommand<String>{

      Stringname;

      CloseableHttpClienthttpClient;

      Stringurl;

         

      public SpeakCacheCommand(String name){

      super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("speakCache")));

      this.name=name;

      this.httpClient=HttpClients.createDefault();

      this.url="http://localhost:8080/speaks?names="+name;

      }

         

      @Override

      protected String getCacheKey(){

      return this.name;

      }

         

      protected String run() throws Exception{

      try{

      HttpGet request=new HttpGet(this.url);

      HttpResponse response=httpClient.execute(request);

      return EntityUtils.toString(response.getEntity());

      }catch(Exceptionex){

      ex.printStackTrace();

      return null;

      }

      }

      }

         

    • 修改启动类

      需要在初始化的时候,开启 HystrixRequestContext 否则无法支持缓存

      package org.lixue.hystrixclient;

         

      import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

         

      public class HystrixClient{

      public static void main(String[]args){

      //需要开启HystrixRequest上下文,合并请求和缓存必须开启

      HystrixRequestContextcontext=HystrixRequestContext.initializeContext();

      try{

      SpeakCacheCommand speakCacheCommand=new SpeakCacheCommand("lixue");

      System.out.println("SpeakCacheCommand execute="+speakCacheCommand.execute()

      +"is cache"+speakCacheCommand.isResponseFromCache());

         

      SpeakCacheCommand speakCacheCommand1=new SpeakCacheCommand("lixue");

      System.out.println("SpeakCacheCommand execute="+speakCacheCommand1.execute()

      +"is cache"+speakCacheCommand1.isResponseFromCache());

         

      context.shutdown();

      }catch(Exceptionex){

      ex.printStackTrace();

      }finally{

      context.shutdown();

      }

      }

      }

         

    • 测试验证

      首先启动 server-provider 项目,然后启动该项目,第一次请求没有重缓存读取,而第二次请求由于时相同的请求参数,因此从缓存读取,输出如下:

      SpeakCacheCommand execute={"lixue":"Hello World lixue Port=8080"} is cache false

      SpeakCacheCommand execute={"lixue":"Hello World lixue Port=8080"} is cache true

         

  • 相关阅读:
    [原创]利用Browser协议探测内网主机操作系统版本(无需端口无视防火墙)
    [EXP]Microsoft Windows 10 (Build 17134)
    [EXP]Microsoft Windows
    [EXP]Apache Spark
    [EXP]Adobe ColdFusion 2018
    [EXP]ThinkPHP 5.0.23/5.1.31
    [EXP]Cisco RV110W
    [EXP]Huawei Router HG532e
    [EXP]Microsoft Windows CONTACT
    [EXP]Microsoft Windows 10
  • 原文地址:https://www.cnblogs.com/li3807/p/8780980.html
Copyright © 2020-2023  润新知