写点什么

Hystrix在项目中实践

2019 年 9 月 26 日

Hystrix在项目中实践

1 背景


我们的业务通常会大量依赖于第三方提供的服务,这些服务都是基于 Http 请求,然后依赖方的服务能力又是参次不齐的,因此,第三方资源的稳定性会直接影响着我们系统的稳定性。简单的调用模式如图 1 表示:



图 1 调用关系时序图


当某一个依赖的外部服务发生了异常,如果外部服务可以快速失败这到还好,最怕的就是服务响应变慢。可以试想一下,一个 http 请求过去,对方的服务一直在慢慢的进行处理,一直到我们所设置的 http 处理时间达到我们所设置的超时上限。在这个过程当中,很有可能会因为某一个外部资源响应速度变慢,而会长时间占用我们的 http 连接池,导致其他可以正常提供服务的资源也无法正常运行,严重的情况下甚至会产生雪崩效应。


如图 2 所示:当依赖的服务 F 发生了严重超时的情况时,所有与服务 F 相关的请求都会堵塞在这里。当后续第 N 个请求来的时候,即使与服务 F 没有依赖关系,但由于前面的请求把资源都占用了,所有其仍然拿不到资源,无法调用外部服务。



图 2 资源耗尽 无法响应


2 为什么引入 Hystrix


Hystrix 是用于分布式场景下服务熔断、降级的开源 Java 库。它的主要作用有线程隔离,熔断,降级和监控报警。这里不会过多的介绍 Hystrix 本身的概念及定义,具体内容可以看其 github 网站或是自行搜索,会有大量的仔细介绍。根据自身业务的具体情况,把外部依赖进行隔离,从而保证某些外部服务出现问题时,其它的服务仍然是可用并且是不受影响的。如图 3 所示:服务 F 发生异常,导致请求 3 无法处理请求,但是请求 4、5、6 仍然可以继续对外提供服务。



图 3 线程池隔离


3 模拟不引入 Hystrix 时


当模拟不引入 Hystrix 时,线程耗尽的情况:


  • 模拟应用调用第三方服务(第三方是我们模拟的一个10秒睡眠的服务)


1  /**2   * controller层3   * @param sleep 睡眠时间,模拟第三方业务的处理时间。4   */5  @RequestMapping(value = "hystrixTest")6  public ApiResult<String> hystrixTest(@RequestParam("sleep") int sleep){7          ApiResult<String> apiResult =iHystrixService.execute();8          return apiResult;9   }
复制代码


 1  /** 2   *service层 3   *service层中调用的/hystrix/sleep地址,就是我们模拟的第三方服务。 4   *@param sleep 睡眠时间,模拟第三方业务的处理时间。 5   *hystrix/sleep地址只是根据传过来的sleep参数进行Thread.sleep()操作。 6   */ 7  public ApiResult<String> execute(int sleep) { 8    try{ 9      System.out.println("开始业务处理 调用Http"10             + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")11             .format(new Date()));1213       // 模拟调用外部服务 逻辑仅是进行Tread.sleep操作模拟处理时间14       ResponseEntity<String> responseEntity = restTemplate15                      .getForEntity("http://127.0.0.1:8080/hystrix/sleep?sleep="16                  +sleep,String.class);1718       System.out.println("调用Http完成 satusCode:"19             +responseEntity.getStatusCode()+" "20             + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")21             .format(new Date()));2223       return ApiResult.SUCCESS();24    }25    catch (Exception e){26      return ApiResult.FAILED();27    }28}
复制代码


  • 配置线程池数量(这里配置成5个)


 1  <!-- 连接池管理器 HttpClient是线程安全的 且可同时执行多个线程的请求  2       管理持久的http连接 节省创建连接时间 --> 3  <!-- 维护的连接数在每个路由基础和总数上都有限制 --> 4  <bean id="pollingConnectionManager" class="org.apache.http.impl.conn 5                                  .PoolingHttpClientConnectionManager"> 6        <!-- 最大连接数 --> 7        <property name="maxTotal" value="5"/> 8        <!-- 每个路由基础的连接 --> 9        <property name="defaultMaxPerRoute" value="5"/>10  </bean>
复制代码


  • 模拟客户端服务调用(这里用的是6个)


我们模拟 6 个并发调用 controller,也就会发起 6 次服务调用,由于我们用 sleep 简单模拟的第三方服务,因此在我们发起第 6 次 controller 请求时,前面的 5 个请求均没有处理完毕返回结果,所以第 6 次的请求会因为拿不到 http 的连接无法调用 hystrix/sleep 这个模拟服务的地址。


从图 3 中我们也可以看出来,有 5 个请求因为拿到了 http 连接资源,可以正常访问外部服务,而有一个则因为当时 http 连接已被耗尽,则不能继续访问第三方服务。日志如图 4 所示:



图 4 有 1 个请求拿不到 http 连接资源


我们可以试想这样一种场景,刚刚 6 个请求,其对应的是 A,B 两个外部服务。假如 A 服务由于某种情况发生响应变慢,而 B 仍然能良好的提供服务,在这种情况下,就会发生由于 A 服务发生问题,而恰巧依赖 A 服务的外部接口又有大量请求,此时就会使得业务系统的资源全部被 A 服务占用,这样业务系统中与 A 服务无关的业务,也无法拿到资源去访问服务良好的 B 服务,最坏的情况就是业务系统中的一台机器被耗尽无法对外提供服务而 down 倒,由于负载均衡或是服务发现的机制,接下来所有的请求又会打到另外的机器上,一直到所有机器都被打爆,发生雪崩效应。


所以我们需要一种机制能够隔离坏的资源,让正常的资源提供正常的服务。这里我们就用到了上面说到的 Hystrix。


本文会介绍 hystrix 的两种使用方式,命令行编码和注解方式。


4Hystrix 的引用


  • 引入maven依赖


 1  <hystrix.core.version>1.5.12</hystrix.core.version> 2  <hystrix.javanica.version>1.5.12</hystrix.javanica.version> 3  <hystrix.metrics.version>1.5.12</hystrix.metrics.version> 4  <dependency> 5      <groupId>com.netflix.hystrix</groupId> 6      <artifactId>hystrix-core</artifactId> 7      <version>${hystrix.core.version}</version> 8  </dependency> 9  <dependency>10      <groupId>com.netflix.hystrix</groupId>11      <artifactId>hystrix-metrics-event-stream</artifactId>12      <version>${hystrix.metrics.version}</version>13   </dependency>14   <dependency>15       <groupId>com.netflix.hystrix</groupId>16       <artifactId>hystrix-javanica</artifactId>17       <version>${hystrix.javanica.version}</version>18   </dependency>
复制代码


5 注解方式使用 Hystrix


5.1 线程池隔离


业务层增加 @HystrixCommand 注解:


 1  @HystrixCommand(groupKey = "HystrixServiceImpl", // groupKey 该命令属于哪个组 2              commandKey = "execute", // 该命令的名称 3              threadPoolKey = "HystrixServiceImpl", // 该命令所属线程池的名称,默认同groupKey 4              fallbackMethod = "executeFallback", // 服务降级方法 5              commandProperties = { 6              @HystrixProperty(name = "execution.isolation.strategy", 7                                                   value = "THREAD"), // 线程池方式 8              @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", 9                                                    value = "5000")   // 超时时间为5秒10  })11  public ApiResult<String> execute() {12     try{13          System.out.println("开始业务处理 调用Http"14                + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")15                  .format(new Date()));1617          ResponseEntity<String> responseEntity = restTemplate18                  .getForEntity("http://127.0.0.1:8080/hystrix/sleep",String.class);1920          System.out.println("调用Http完成 satusCode:"21                + responseEntity.getStatusCode()+" "22                + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")23                  .format(new Date()));2425          return ApiResult.SUCCESS();26      }27      catch (Exception e){28          return ApiResult.FAILED();29      }30  }
复制代码


  • 增加回退方法


1  public ApiResult<String> executeFallback() {2       System.out.println(Thread.currentThread().getId()3              + " "+Thread.currentThread().getName()+" "4              + Thread.currentThread().getState().toString());56      return ApiResult.FAILED("hystrix->executeFallback");7  }
复制代码


  • 模拟超时


模拟第三方执行时间为 10S,而我们在 Hystrix 中设置的为 5S,所以会触发服务降级,应用在超时后执行 executeFallback 降级方法。如图 5 所示:



图 5 服务超时降级


细心点的人可以看出来,虽然客户端返回以及服务端执行了服务降级方法,但是原有的处理逻辑仍然继续执行,对于降级的请求,原有本身处理过程不会被中断。当服务降级到达一定的阀值时会启动断路器,后续一段时间内的请求都将进入降级方法,不会再调用业务逻辑。


  • 使用Hystrix进行线程隔离


新增加一个外部模拟服务 B,用于模拟访问不同的外部依赖, 分别设置其线程池大小为 2 个大小。


 1  @HystrixCommand(groupKey = "HystrixServiceImpl",commandKey = "execute", 2        fallbackMethod = "executeFallback", 3        commandProperties = { 4        @HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"), 5        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", 6                         value = "5000") 7        }, 8        threadPoolProperties = { 9        @HystrixProperty(name = "coreSize",value = "2"),// 线程池大小10        @HystrixProperty(name = "maQueueSize",value = "-1")// -1 表示不等待直接拒绝11  })
复制代码


  • 模拟4个客户端访问系统资源,其中3个访问A资源1个访问B资源。如图6所示:



图 6 4 个客户端请求 AB 两个资源


从日志可以看出,访问 A 资源的 3 个客户端,有 2 个成功,1 个返回 fallback 结果,这是因为我们设置的 Hystrix 的线程池大小为 2,所以第三个请求来的时候获取不到可用的线程池资源。而总的 restTemplate 线程池之前设置的为 5,此时仍有连接资源,所以 B 资源可以继续访问,而 A 由于业务处理的慢则没有连接返回释放资源,所以 A 的后继请求都是失败。


通过这种方式,可以起到线程隔离的效果,但是当所有的线程隔离池中当前所使用的总线程数大于了我们设置的 http 连接总数,总体业务当然还是失败的。Hystrix 只是起到了线程池资源隔离的作用,所以具体每个线程池设置多大的参数,还是要经过开发人员线上真实情况动态调整。


5.2 并发量


Hystrix 除了支持线程池外,还支持信号量方式控制请求并发数。


 1// 修改HystrixCommand为信息量方式 2@HystrixCommand(groupKey = "HystrixServiceImpl", 3                commandKey = "execute", 4                fallbackMethod = "executeFallback", 5      commandProperties = { 6      @HystrixProperty(name = "execution.isolation.strategy" 7                       ,value = "SEMAPHORE"), // 信号量方式 8      @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests" 9                       ,value = "3") // 最大并发数为310})
复制代码


客户端模拟 4 个并发请求,只有 3 个是正常返回业务结果,另一个超过了并发数量限量触发了 fallback 方法。如下图 7:



图 7 并发日志


5.3 小结


通过上面的例子,可以了解到使用注解的方式使用 Hystrix 简单,易用并且开发量小。但其也有一些缺点,在实际应用当中,我们需要随业务的具体情况而动态的调整 Hystrix 参数,尤其是超时时间和并发数数量这些重要参数。另外,如果是对老业务的改造,使用注解方式需要把原有的所有外部依赖的地方都修改一下,其工作量也是不小。


6 命令式编辑使用 Hystrix


6.1 使用方式


上面介绍了在项目中使用 Hystrix 注解的方式对服务进行隔离和降级的注解方式。虽然简单、方便、易与使用,但是对于老业务来讲,把所有的方法都要加上 Hystrix 注解的话也不太友好,而且也不算灵活,无法做到动态修改。因此,想到使用 AOP 的方式,通过命令式编程对目标方式进行拦截,对需要使用服务降级的方法进行 Hystrix 操作,完全基于配置,这样就无需对原有的业务代码进行改动。


  • 服务降价配置说明


 1  # 对offerService.ocrIdCardImg方法进行降级配置 true:启用hystrix降级逻辑 false:不受hystrix控制 2  hystrix.method.com.ke.life.thirdpart.service.offerService.ocrIdCardImg=true 3  # 对offerService.ocrIdCardImg方法设置服务超时时间 4  hystrix.timeout.com.ke.life.thirdpart.service.offerService.ocrIdCardImg=1000 5  # 当使信号量时,对offerService.ocrIdCardImg进行并发数设置 6  hystrix.requestSize.com.ke.life.thirdpart.service.offerService.ocrIdCardImg=50 7  # 并发策略  SEMAPHORE或THREAD 也可以不配置默认为线程池 8  hystrix.isolationStrategy.com.ke.life.thirdpart.service.offerService.ocrIdCardImg=THREAD 9  # offerService.ocrIdCardImg触发hystrix服务降级时,返回的降级数据 void方法可以不配置10  hystrix.default.return.com.ke.life.thirdpart.service.offerService.ocrIdCardImg={"errno":"-1","msg":"服务降级"}
复制代码


此外,我们还有一些是业务的开关,在代码中是通过 ISystemSettingService 接口逻辑业务开关,代码中会有说明。


  • Hystrix拦截器代码结构说明


 1  @Component 2  @Aspect 3  public class HystrixCommandAdvice { 4 5      private static final Logger logger = LoggerFactory.getLogger(HystrixCommandAdvice.class); 6 7      // 服务降级方法配置前缀 8      private static String HYSTRIX_METHOD_PREFIX="hystrix.method."; 910      // 服务降级方法超时时间配置前缀11      private static String HYSTRIX_TIMEOUT_PREFIX="hystrix.timeout.";1213      // 服务降级方法并发数配置前缀14      private static String HYSTIRX_REQUEST_SIZE_PREFIX="hystrix.requestSize.";1516      // 服务降级方法策略配置前缀 使用信号量还是线程池17      private static String HYSTRIX_ISOLATION_STRATEGE_PREFIX = "hystrix.isolationStrategy.";1819      // 服务降级方法返回类型配置前缀20      private static String HYSTRIX_DEFAULT_RETURN_PREFIX="hystrix.default.return.";2122      // iSystemSettingService获取业务中的配置,可以来自数据库、redis以及配置中心23      @Autowired24      private ISystemSettingService iSystemSettingService;2526      // 定义切点,拦截某些特定第三方服务27      @Pointcut("execution(* com.ke.life.thirdpart.service.*.*(..))")28      public void hystrixPointcut(){}2930      /**31       * 切面32       */33      @Around("hystrixPointcut()")34      public Object runCommand(final ProceedingJoinPoint pJoinPoint)  throws Throwable {35          // 此处省略,下面有具体代码说明36      }3738      /**39       *  Hystrix参数设置40       */41      private HystrixCommand.Setter setter(String commonGroupKey,String commandKey,42                                           int timeout,int requestSize,43                                           String strategy) {44           // 此处省略,下面有具体代码说明45      }4647      /**48       * 生成降级后的返回内容49       */50      public static Object generateClass(String clazzPackage , String defaultValue) 51                                                                  throws Exception{52          // 此处省略,下面有具体代码说明53  }
复制代码


  • runCommand方法代码


 1  @Around("hystrixPointcut()") 2  public Object runCommand(final ProceedingJoinPoint pJoinPoint) throws Throwable { 3 4     // 获取服务签名 public xxxx.returnObject.class. com.xxxx.serice.method() 5     String signature = pJoinPoint.getSignature().toLongString(); 6     String serviceMethod = signature.substring(signature.lastIndexOf(" ")+1, 7              signature.lastIndexOf("(")); 8     boolean hystrixServiceBoolean = DynamicPropertyFactory.getInstance() 9          .getBooleanProperty(HYSTRIX_METHOD_PREFIX+serviceMethod, false).get();1011     // 降级开关打开状态并且配置此业务方法的降级方案12     // iSystemSettingService为我们自己定义的字典表,保存业务的开关和配置信息 13     if(true == hystrixServiceBoolean 14            && "true".equals(iSystemSettingService.getByKey("hystrix.switch"))){15            // 使用Hystrix服务降级封装的方法,具体代码下面有示例16            return wrapWithHystrixCommnad(pJoinPoint).execute();17     }18     // 没有配置此业务方法的降级方案,则直接执行19     try {20         return pJoinPoint.proceed();21     } catch (Throwable throwable) {22         throw (Exception) throwable;23     }24  }
复制代码


  • 服务方法封装Hystirx功能,wrapWithHystrixCommnad代码


 1  // 使用hystrix封装业务执行的方法 2  private HystrixCommand<Object> wrapWithHystrixCommnad(final ProceedingJoinPoint  3                                                                       pJoinPoint) { 4          // 获取降级服务的方法签名  5          //如:public xxxx.return.class com.xxxx.serice.method() 6          String signature = pJoinPoint.getSignature().toLongString(); 7          // 解析出方法签名中返回的类型 如:xxxx.return.class 8          String returnClass = signature.substring(signature.indexOf(" ")+1 9                                                   ,signature.lastIndexOf(" "));10          // 解析出就去签名中的方法 如:com.xxxx.serice.method11          String serviceMethod = signature.substring(signature.lastIndexOf(" ")+112                                                    ,signature.lastIndexOf("("));1314          // 获取配置文件中的配置数据,6.2章节中有具体说明15          DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory16                                                          .getInstance();17          // 超时时间 默认5秒18          int timeout = dynamicPropertyFactory19          .getIntProperty(HYSTRIX_TIMEOUT_PREFIX+serviceMethod, 5000).get();2021          // 返回值类型 默认空即void22          String defaultReturn = dynamicPropertyFactory23          .getStringProperty(HYSTRIX_DEFAULT_RETURN_PREFIX+serviceMethod,"").get();2425          // 请求数大小 默认50个 只有在策略使用信号量时再会用到26          int  requestSize= dynamicPropertyFactory27          .getIntProperty(HYSTIRX_REQUEST_SIZE_PREFIX+serviceMethod, 50).get();2829          // 隔离策略30          String strategy= dynamicPropertyFactory31          .getStringProperty(HYSTRIX_ISOLATION_STRATEGE_PREFIX+serviceMethod, "THREAD")32          .get();33          String declaringTypeName = pJoinPoint.getSignature()34          .getDeclaringTypeName();35          String commandKey = pJoinPoint.getSignature().getName();36          String commonGroupKey = declaringTypeName37          .substring(declaringTypeName.lastIndexOf(".")+1)+"."+commandKey;3839          return new HystrixCommand<Object>(setter(commonGroupKey,commandKey40              ,timeout,requestSize,strategy)) {4142              @Override43              protected Object run() throws Exception {44                  try {45                      Object object  = pJoinPoint.proceed();46                      return object;47                  } catch (Throwable throwable) {48                      throw (Exception) throwable;49                  }50              }51              /**52               * 以下四种情况将触发getFallback调用:53               * 1)run()方法抛出非HystrixBadRequestException异常54               * 2)run()方法调用超时55               * 3)断路器开启拦截调用56               * 4)线程池/队列/信号量是否跑满57               * 实现getFallback()后,执行命令时遇到以上4种情况将被fallback接管,58               * 不会抛出异常或其他59               */60              @Override61              protected Object getFallback() {6263                logger.info("服务触发了降级 {} 超时时间 {} 方法返回类型 {} 是否启动了熔断 {}" 64                    ,serviceMethod,timeout,returnClass,this.isCircuitBreakerOpen());65                try{66                     return generateClass(returnClass,defaultReturn);67                }68                catch (Exception e){69                    logger.error("生成降级返回对象异常",e);70                }71                return  null;72              }73          };74      }
复制代码


  • Hystrix参数设置(AOP方式同样也可以设置为线程池方式和信号量方式),setter方法


 1  // hystrix参数设置 2  private HystrixCommand.Setter setter(String commonGroupKey,String commandKey 3          ,int timeout,int requestSize,String strategy) { 4    // 使用信号量 5    if("SEMAPHORE".equalsIgnoreCase(strategy)){ 6        return HystrixCommand.Setter 7               .withGroupKey(HystrixCommandGroupKey.Factory 8                   .asKey(commonGroupKey==null?"commonGroupKey":commonGroupKey)) 9                   .andCommandKey(HystrixCommandKey.Factory10                   .asKey(commandKey==null?"commandKey":commandKey))11                   // 信号量12                   .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()13                   .withExecutionIsolationStrategy(HystrixCommandProperties14                   .ExecutionIsolationStrategy.SEMAPHORE)15                   // 并发数量16                   .withExecutionIsolationSemaphoreMaxConcurrentRequests(requestSize)17                   .withExecutionTimeoutInMilliseconds(timeout));18     }19     else {20        // 使用线程池21        return HystrixCommand.Setter22               .withGroupKey(HystrixCommandGroupKey.Factory23                    .asKey(commonGroupKey==null?"commonGroupKey":commonGroupKey))24                    .andCommandKey(HystrixCommandKey.Factory25                    .asKey(commandKey==null?"commandKey":commandKey))26                    // 线程池大小27                    .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()28                                                    .withCoreSize(requestSize))29                    .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()30                    // 线程池31                    .withExecutionIsolationStrategy(HystrixCommandProperties32                                                   .ExecutionIsolationStrategy.THREAD)33                    // 超时时间34                    .withExecutionTimeoutInMilliseconds(timeout)35                    .withExecutionIsolationThreadInterruptOnTimeout(true)36                    .withExecutionTimeoutEnabled(true));37          }38      }
复制代码


  • 生成降级返回的数据,generateClass方法


 1  /** 2   * @param clazzPackage 返回的类 3   * @param defaultValue 降级后返回的值 4   */ 5  public static Object generateClass(String clazzPackage , String defaultValue) throws  6                                                                           Exception{ 7 8      if(StringUtils.isBlank(clazzPackage) || StringUtils.isBlank(defaultValue)){ 9              return null;10      }1112      logger.info("返回的类"+ clazzPackage);13      // 返回的类型是String类型的14      if(clazzPackage.contains("String")){15           String returnStr = new String(defaultValue.getBytes("iso8859-1"),"UTF-8");16           logger.info("降级返回的字符串 {}",returnStr);17           return returnStr;18      }1920      JSONObject hystrixReturnValueJson = JSONObject.parseObject(defaultValue);21      Iterator iterator = hystrixReturnValueJson.keySet().iterator();22      HashMap<String,String> fieldMap = new HashMap<>();23      while(iterator.hasNext()){24          String key = (String)iterator.next();25          String value = new String(hystrixReturnValueJson.getString(key)26                                    .getBytes("iso8859-1"),"UTF-8");27          logger.info("字段属性"+ key +"  "+value);28          fieldMap.put(key,value);29      }3031      // 使用反射,生成降级后返回的对象32      Class clazz = Class.forName(clazzPackage);33      Object object = clazz.newInstance();34      BeanUtils.populate(object,fieldMap);3536      logger.info("生成的降级返回类 {} {}",object.toString(),JSON.toJSONString(object));37      return object;38   }
复制代码


6.2. 动态修改配置


Hystrix 的超时时间,线程池大小以及并发数,都需要针对不对的场景和情况进行动态调整,所以动态配置是非常有必要的。例如:服务是从网关层统一转发到后端的业务应用上,那么网关层做动态限流就很有必要。随着后端业务机器及能力的变化可以在不重启网关服务的情况下随时动态修改网关的流量配置。


携程的 Apollo 和阿里的 Diamond 都是比较好的动态配置管理工具,界面也对用户友好。本文演示使用的是 netflix 的 Archaius。


Archaius 默认会自动读取项目类路径下的 config.properties 文件并且每分钟去重新加载下属性配置。


maven 引入 archaius 包


1   <dependency>2    <groupId>com.netflix.archaius</groupId>3    <artifactId>archaius-core</artifactId>4    <version>0.6.0</version>5   </dependency>
复制代码


  • 使用方式


在上面的示例代码中,其实就已经使用到了 archaius,其使用方式也是非常的简单


1  DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();2  // 获取config.properties属性文件中key为hystrix.timeout的值。如果取不到,则使用默认值3  int value = dynamicPropertyFactory.getIntProperty("hystrix.timeout", 5000).get();
复制代码


6.3 小结


使用 AOP 的方式,我个人感觉是更灵活些,也利于对 Hystrix 做统一管理,更加模块化也更收敛。最重要的是,对于老项目接入 Hystrix 的项目来说,改动量相对要小一些。只要设计出适合业务自身的降级逻辑就可以了,在 AOP 中统一配置实现。


7 断路器


Hystrix 本身就提供熔断功能。当服务降级达到一个阀值时,断路器会自动开启,断路器开启后,所有的服务会被拦在外面。


断路器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值决定的。


下图 8 显示了断路器开关的流程。


服务的健康状况=请求失败数/请求总数



图 8 断路器


每个断路器默认维护 10 个 bucket,每秒一个 bucket,每个 bucket 记录成功、失败、超时、拒绝的状态,默认为错误超过 50%且 10 秒内超过 20 个请求进行中断拦截。


这里特别要说明一下 10 秒内超过 20 个请求,它的意思是说在统计窗口 10 秒内有至少有 20 个请求并且失败率为 50%,即一个失败就发生熔断。但是, 如果在 10 秒内不足 20 个请求,即使失败率为 100%,也是不满足断路器开启条件的。


可以通过 hystrix.command.default.circuitBreaker.requestVolumeThreshold 配置修改窗口内的失败次数。


8 总结


伴随着分布式应用和服务化以及微服务的开发方式已成为了主流,所以服务间的调用越来越复杂,也越来越频繁。那么如何构建一个高可用和高并发的服务,就变成了我们最为棘手的问题。Hystrix 可以有效的隔离外部坏的服务来保护自己。在流量高峰超过系统处理能力的时候,可以进行快速失败,保证已接收处理的请求不受影响。此外,Hystrix 也被纳入了 spring-cloud 体系,足可以看出其在微服务中的重要性。


作者介绍:


王大可(企业代号名),贝壳找房后端研发工程师。


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/4Fg0COnWRB3rRWfxbJt7gA


2019 年 9 月 26 日 16:11847

评论

发布
暂无评论
发现更多内容

云图说 | 云上资源管控有神器!关于IAM,你想知道的都在这里!

华为云开发者社区

服务 权限管理 iam

如何在数智化时代少走弯路? 这里有100个案例可以借鉴

京东智联云开发者

DevOps 云原生

图文回顾丨北京「解构云原生:企业数字化转型新支点」沙龙

RancherLabs

k8s rancher

建信金科大咖访谈:金融衍生品定价与建行实践

金科优源汇

金融科技 金融创新

搭建网站/APP最全准备攻略

前嗅大数据

小程序 建站 APP发布

EMAS远程日志 - 移动端问题排查利器

应用研发平台EMAS

阿里云 运维 日志 监控告警 应用

耗时一个月整理的97道大厂Java核心面试题出炉,精心整理,无偿分享

Java架构之路

Java 程序员 架构 面试 编程语言

太赞了!滴滴开源了一套分布式ID的生成系统...

Java架构师迁哥

云南区块链电子发票全面推广啦!

CECBC区块链专委会

区块链 纳税人

传统巨头抢占区块链场景高地 医疗、汽车、金融成为热门赛道

CECBC区块链专委会

区块链 金融

信任的传递——为什么我们需要第三方授权?

ThoughtWorks洞见

证书 身份认证

一口气说出四种幂等性解决方案,面试官露出了姨母笑~

不才陈某

Java 分布式 接口

即构实时音视频多中心调度设计

ZEGO即构

第十三周学习总结

饭桶

面向全场景模块化设计 京东智联云的服务器部署有多灵活?

京东智联云开发者

服务器 云主机

Elasticsearch 新机型发布,性能提升30%

小小的一朵云

大数据 elasticsearch Elastic Stack

80%Java开发者面试都问的SpringBoot你竟不会?看完这些笔记足以

Java架构之路

Java 程序员 架构 面试 编程语言

VACUUM无法从表中删除死元组的三个原因

PostgreSQLChina

数据库 postgresql

第13周作业

饭桶

Shell脚本命令常用技巧

MySQL从删库到跑路

shell脚本编写

云原生架构-可观测性之 Prometheus 服务自动发现

云原生实验室

浅谈JDK并发包下面的分治思想及分治思想在高并发场景的运用

AI乔治

Java 架构 jdk 分布式 多线程与高并发

Java进阶文档:彻底搞懂JVM+Linux+MySQL+Netty+Tomcat+并发编程

Java架构之路

Java 程序员 架构 面试 编程语言

倾斜摄影实景三维在智慧工厂 Web 3D GIS 数字孪生应用

一只数据鲸鱼

GIS 数字化 数据可视化 3D渲染 数字工厂

Java中多线程安全问题实例分析

叫练

Java 多线程 什么是多线程 多线程与高并发

附PPT丨AI和云原生时代的数据库进化之路

dbaplus社群

数据库 云原生

一文为你详解Unique SQL原理和应用

华为云开发者社区

数据库 sql unique

住建部等六部门:广泛运用区块链等技术,建设智慧物业管理服务平台

CECBC区块链专委会

物业生活

内存问题探微

AI乔治

Java 架构 编程语言 内存 多线程与高并发

AWS 助力贝壳VR看房走出国门,升级全球居住服务新体验

亚马逊AWS官方博客

AWS

记一次MapReduce的内存溢出

AI乔治

Java mapreduce 架构 内存溢出

Hystrix在项目中实践-InfoQ