降级特技之使用Hystrix实现降级和熔断—《亿级流量网站架构核心技术》

使用Hystrix实现降级

  通过配置中心可以人工进行降级,而我们也需要根据服务的超时时间进行自动降级,本部分将演示使用Hystrix实现超时自动降级。Hystrix介绍请参考“第3章 隔离术”中的Hystrix简介部分。

  public class GetStockServiceCommand extends HystrixCommand<String> {

  private StockService stockService;

  public GetStockServiceCommand(StockService stockService) {

  super(setter());

  this.stockService= stockService;

  }

  private static Setter setter() {

  //服务分组

  HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory. asKey("stock");

  ……

  //命令配置

  HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()

  ……

  .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)

  .withFallbackEnabled(true)//默认true

  .withFallbackIsolationSemaphoreMaxConcurrentRequests(100)//默认10

  .withExecutionIsolationThreadInterruptOnFutureCancel(true) //默认false

  .withExecutionIsolationThreadInterruptOnTimeout(true)//默认true

  .withExecutionTimeoutEnabled(true) //默认true

  .withExecutionTimeoutInMilliseconds(1000)//默认1000

  ;

  return HystrixCommand.Setter

  .withGroupKey(groupKey)

  .andCommandPropertiesDefaults(commandProperties);

  }

  @Override

  protectedString run() throws Exception {

  return stockService.getStock();//可以通过抛出异常,或Thread.sleep模拟超时

  }

  @Override

  protected String getFallback() {//降级方法

  return "有货";

  }

  }

  整体执行流程如下图所示。

  

  首先,Command会调用run方法,如果run方法超时或者抛出异常,如果启用了降级处理,则调用getFallback方法进行降级。

  而降级处理主要进行两部分处理:HystrixCommandProperties配置和getFallback降级处理方法。首先,我们看下HystrixCommandProperties配置。

  withFallbackEnabled:是否启用降级处理,如果启用了,则在超时或异常时调用getFallback进行降级处理,默认开启。

  withFallbackIsolationSemaphoreMaxConcurrentRequests:fallback方法的信号量配置,配置getFallback方法并发请求的信号量,如果请求超过了并发信号量限制,则不再尝试调用getFallback方法,而是快速失败,默认信号量为10。

  withExecutionIsolationThreadInterruptOnFutureCancel:当隔离策略为THREAD时,当执行线程执行超时时,是否进行中断处理,即Future#cancel(true)处理,默认为false。

  withExecutionIsolationThreadInterruptOnTimeout:当隔离策略为THREAD时,当执行线程执行超时时,是否进行中断处理,默认为true。

  withExecutionTimeoutEnabled:是否启用执行超时机制,默认为true;

  withExecutionTimeoutInMilliseconds:执行超时时间,默认为1000毫秒,如果命令是线程隔离,且配置了executionIsolationThreadInterruptOnTimeout=true,则执行线程将执行中断处理。如果命令是信号量隔离,则进行终止操作,因为信号量隔离与主线程是在一个线程中执行,其不会中断线程处理,所以要根据实际情况来决定是否采用信号量隔离,尤其涉及网络访问的情况。

  当开启了降级处理,run方法超时或者异常时将会调用getFallback处理,getFallback需要注意以下几点。

  ● 其最大并发数受fallbackIsolationSemaphoreMaxConcurrentRequests控制,因此,如果失败率非常高,则要重新配置该参数,如果最大并发数超了该配置,则不会再执行getFallback,而是快速失败,抛出如“HystrixRuntimeException: GetStockServiceCommand fallback executionrejected”类似的异常。

  ● 该方法不能进行网络调用,应该只是缓存的数据,或者静态数据(如我们的库存方法返回有货)。

  ● 如果必须走网络调用,则应该在getFallback方法中调用另一个Command实现,通过Command可以有降级和熔断机制保护应用,而getFallback只有fallbackIsolationSemaphoreMaxConcurrentRequests参数控制最大并发数。

  在使用Command的业务代码处,可以使用如下方法获取执行的状态。

  isResponseTimedOut:是否响应超时了。

  isFailedExecution:是否执行失败了,如抛出了异常。

  getFailedExecutionException:获取失败后的执行异常,即run方法抛出的异常。

  isResponseFromFallback:是否是getFallback返回的响应。

  使用Hystrix实现熔断

  熔断机制实现

  Hystrix提供了熔断实现,熔断后会自动降级处理,如下图所示。

  

  Command首先调用HystrixCircuitBreaker#allowRequest判断是否熔断了,如果没有熔断,则执行Command#run方法正常处理,如果熔断了,则直接调用降级方法Command#getFallback方法降级处理。

  接下来,我们先看下HystrixCircuitBreakerImpl#allowRequest方法实现。

  public boolean allowRequest() {

  //1、如果熔断开关强制打开,则熔断降级处理

  if (properties.circuitBreakerForceOpen().get()){

  return false;

  }

  //如果熔断开关强制闭合,则正常处理

  if (properties.circuitBreakerForceClosed().get()){

  //还是需要调用isOpen方法进行采样处理

  isOpen();

  return true;

  }

  //正常判断

  return !isOpen() || allowSingleTest();

  }

  //允许在一个时间窗口内进行单次访问测试

  public boolean allowSingleTest() {

  //熔断开关打开时,最后一次测试时间

  long timeCircuitOpenedOrWasLastTested= circuitOpenedOrLastTestedTime.get();

  //如果熔断开关处于打开状态,且在一个时间窗口内(circuitBreakerSleepWindowInMilliseconds),则允许一次访问进行测试

  if (circuitOpen.get() &&System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested+properties.circuitBreakerSleepWindowInMilliseconds().get()){

  if(circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested,System.currentTimeMillis())) {

  return true;

  }

  }

  return false;

  }

  @Override

  public boolean isOpen() {

  //如果熔断开关处于打开状态,则熔断降级处理

  if (circuitOpen.get()){

  return true;

  }

  //熔断开关当前处于闭合状态,需要根据采样判断当前是否需要熔断

  HealthCounts health = metrics.getHealthCounts();

  //如果当前采样的总请求数小于circuitBreakerRequestVolumeThreshold阀值,则不进行熔断

  if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {

  return false;

  }

  //如果当前采样的错误率小于circuitBreakerErrorThresholdPercentage阀值,则不进行熔断

  //errorPercentage = errorCount / totalCount * 100

  if (health.getErrorPercentage()< properties. circuitBreakerErrorThresholdPercentage().get()) {

  return false;

  } else {

  //当前失败率超过了阀值,进行熔断降级处理

  if (circuitOpen.compareAndSet(false,true)) {

  circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());

  return true;

  } else{

  return true;

  }

  }

  }

  当我们的熔断开关处于打开状态时,此时是不允许任何请求处理的,而是直接降级处理,但是提供了markSuccess方法,当请求处理成功时进行熔断开关闭合。

  public void markSuccess() {

  if (circuitOpen.get()){

  if(circuitOpen.compareAndSet(true, false)) {

  //重置health采样,不影响其他采用

  metrics.resetStream();

  }

  }

  }

  通过circuitBreakerSleepWindowInMilliseconds可以控制一个时间窗口内可进行一次请求测试,如果测试成功,则闭合熔断开关,否则还是打开状态,从而实现了快速失败和快速恢复。

  关于熔断开关需要知道如下几个概念。

  闭合(Closed):如果配置了熔断开关强制闭合,或者当前请求失败率没有超过失败率阀值,则熔断开关处于闭合状态,不启动熔断机制,即不进行降级处理。

  打开(Open):如果配置了熔断开关强制打开,或者当前失败率超过失败率阀值,则熔断开关打开,启动熔断机制,根据配置调用降级处理方法getFallback进行降级处理。

  半打开(Half-Open):当熔断处于打开状态后,不能一直熔断下去,需要在一个时间窗口后进行重试,这种状态就是半打开。Hystrix允许在circuit BreakerSleepWindowInMilliseconds窗口内进行一次重试,重试成功则闭合熔断开关,否则熔断开关还是处于打开状态。

  那什么样的请求被认为是错误呢,HealthCounts在统计错误数量时使用如下方法。

  public HealthCounts plus(long[] eventTypeCounts) {

  long updatedTotalCount= totalCount;

  long updatedErrorCount= errorCount;

  long successCount =eventTypeCounts[HystrixEventType.SUCCESS. ordinal()];

  long failureCount =eventTypeCounts[HystrixEventType.FAILURE. ordinal()];

  long timeoutCount =eventTypeCounts[HystrixEventType.TIMEOUT. ordinal()];

  long threadPoolRejectedCount= eventTypeCounts[HystrixEventType. THREAD_POOL_REJECTED.ordinal()];

  long semaphoreRejectedCount= eventTypeCounts[HystrixEventType. SEMAPHORE_REJECTED.ordinal()];

  updatedTotalCount += (successCount + failureCount + timeoutCount +threadPoolRejectedCount + semaphoreRejectedCount);

  updatedErrorCount += (failureCount+ timeoutCount + threadPoolRejectedCount + semaphoreRejectedCount);

  return new HealthCounts(updatedTotalCount, updatedErrorCount);

  }

  即失败(如异常)、超时、线程池拒绝、信号量拒绝数量总和是失败总数。

  配置示例

  下面是HystrixCommandProperties的熔断参数配置。

  HystrixCommandProperties.Setter commandProperties =HystrixCommandProperties. Setter()

  ……

  .withCircuitBreakerEnabled(true)//默认为true

  .withCircuitBreakerForceClosed(false)//默认为false

  .withCircuitBreakerForceOpen(false)//默认为false

  .withCircuitBreakerErrorThresholdPercentage(50)//默认为50%

  .withCircuitBreakerRequestVolumeThreshold(20) //默认为20

  .withCircuitBreakerSleepWindowInMilliseconds(5000)//默认为为5s

  具体配置含义如下所示。

  withCircuitBreakerEnabled:是否开启熔断机制,默认为true。

  withCircuitBreakerForceClosed:是否强制关闭熔断开关,如果强制关闭了熔断开关,则请求不会被降级,一些特殊场景可以动态配置该开关,默认为false。

  withCircuitBreakerForceOpen:是否强制打开熔断开关,如果强制打开可熔断开关,则请求强制降级调用getFallback处理,可以通过动态配置来打开该开关实现一些特殊需求,默认为false。

  withCircuitBreakerErrorThresholdPercentage:如果在一个采样时间窗口内,失败率超过该配置,则自动打开熔断开关实现降级处理,即快速失败。默认配置下采样周期为10s,失败率为50%。

  withCircuitBreakerRequestVolumeThreshold:在熔断开关闭合情况下,在进行失败率判断之前,一个采样周期内必须进行至少N个请求才能进行采样统计,目的是有足够的采样使得失败率计算正确,默认为20。

  withCircuitBreakerSleepWindowInMilliseconds:熔断后的重试时间窗口,且在该时间窗口内只允许一次重试。即在熔断开关打开后,在该时间窗口允许有一次重试,如果重试成功,则将重置Health采样统计并闭合熔断开关实现快速恢复,否则熔断开关还是打开状态,执行快速失败。

  熔断后将降级调用getFallback进行处理(fallbackEnabled=true),通过Command如下方法可以判断是否熔断了。

  isCircuitBreakerOpen:熔断开关是否打开了,通过“circuitBreakerForceOpen().get()|| (!circuitBreakerForceClosed().get() && circuitBreaker.isOpen())”判断。

  isResponseShortCircuited:isCircuitBreakerOpen=true,且调用getFallback时返回true。

  采样统计

  Hystrix在内存中存储采样数据,支持如下两种采样。

  BucketedCounterStream:计数统计,比如记录一定时间窗口内的失败、超时、线程池拒绝、信号量拒绝数量,记录N组。写入数据时写到第N组,统计时使用前N-1组数据,因为第N个刚开始统计时是随时变化的。然后基于时间滚转采样分组即可。

  

  采样统计滚转时间窗口为10s,每秒1个分组(桶),即每秒采样一次,每个分组记录着当前桶的成功、失败、超时、线程拒绝统计数量。

  RollingConcurrencyStream:最大并发数统计,如Command/ThreadPool的最大并发数。

  RollingDistributionStream:延时百分比统计,同HystrixRollingNumber类似,差别在于其是百分位数的统计。比如每组记录P(比如100)个数值,统计时使用前N-1组数据,将分组内数据按从小到大排序,然后累加,处于第p%位置的数值就是p百分位数,通过它可以实现P50、P99、P999,Hystrix用来统计时延的分布情况。最新版本Hystrix使用HdrHistogram库来实现统计。

  3.1 Command、ThreadPool计数/最大并发采样统计

  HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter()

  ……

  .withMetricsRollingStatisticalWindowInMilliseconds(1000)

  .withMetricsRollingStatisticalWindowBuckets(10);

  HystrixCommandProperties.Setter commandProperties =HystrixCommandProperties. Setter()

  ……

  .withMetricsRollingStatisticalWindowInMilliseconds(10000)

  .withMetricsRollingStatisticalWindowBuckets(10);

  withMetricsRollingStatisticalWindowInMilliseconds:配置采样统计滚转时间窗口,默认为10s。

  withMetricsRollingStatisticalWindowBuckets:配置采用统计滚转时间窗口内的桶的总数量,默认为10,比如时间窗口为10000,桶数量为10,则采样统计间隔为每秒一个桶统计。

  3.2 Command健康度采样统计

  HystrixCommandProperties.Setter commandProperties =HystrixCommandProperties. Setter()

  ……

  .withMetricsRollingStatisticalWindowInMilliseconds(10000)

  .withMetricsHealthSnapshotIntervalInMilliseconds(500);

  withMetricsRollingStatisticalWindowInMilliseconds:同上所示。

  withMetricsHealthSnapshotIntervalInMilliseconds:记录健康采用统计的快照频率,默认为500ms,即500ms一个采样统计间隔,那么桶的数量为10000/500=20个。

  该统计在熔断机制中使用,如果计算熔断的频率非常高,则要控制好采样的频率,如果太频繁,那么将造成CPU计算密集,如10ms一个周期,因为会对前N-1个桶进行统计,计算累加时会耗费CPU。所以选择Hystrix要注意此处的性能消耗和调优。如果此处是性能瓶颈,则可以废掉统计,或者按照Hystrix思路实现自己的降级组件。

  3.3 Command时延分布采样统计

  HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()

  ……

  .withMetricsRollingPercentileWindowInMilliseconds(60000)

  .withMetricsRollingPercentileWindowBuckets(6);

  同withMetricsRollingStatisticalWindowInMilliseconds和withMetricsRollingStatisticalWindowBuckets,默认采样滚转时间窗口为60s,总共6个桶,即采样统计间隔为每10秒一个桶统计。

  4.统计结果

  可以调用Command#getMetrics获取采样统计,然后通过HystrixCommandMetrics相关方法获取统计数据。

  getExecutionTimePercentile(50);//P50

  getExecutionTimePercentile(99);//P99

  getExecutionTimePercentile(999);//P999

  也可以订阅HystrixDashboardStream.getInstance()进行统计。Hystrix提供了hystrix-dashboard进行图形化展示。

  接下来我们通过turbine + hystrix-dashboard实现集群化的统计可视化。

  

  首先,Hystrix应用会暴露统计接口,然后Turbine会聚合这些统计数据,Hystrix Dashboard会拉取聚合后的统计信息展示到仪表盘上。

  5.Hystrix客户端添加暴露统计信息Servlet

  @Bean

  public ServletRegistrationBean servletRegistrationBean() {

  returnnew ServletRegistrationBean(new HystrixMetricsStreamServlet(), "/hystrix.stream");

  }

  在我们Hystrix客户端添加如上spring-boot代码配置,然后就可以访问如http://127.0.0.1:9080/hystrix.stream获取到统计数据。

  6.部署Turbine

  下载Turbine WAR包(本文使用的是Turbine 1.0.0),部署到Tomcat中,然后修改WEB-INF/classes/config.properties配置,启动Tomcat。

  turbine.ConfigPropertyBasedDiscovery.default.instances=127.0.0.1

  turbine.instanceUrlSuffix=:9080/hystrix.stream

  配置Hystrix应用的IP和获取统计信息的URL path部分,组合后拉取统计信息。访问如http://127.0.0.1:8080/turbine/turbine.stream获取聚合后的统计数据。

  7.部署Hystrix Dashboard

  下载 hystrix-dashboard WAR包(本文使用的是hystrix-dashboard 1.5.6),部署到Tomcat中,然后启动Tomcat。访问如http://127.0.0.1:8080/hystrix-dashboard启动仪表盘。

  在如下界面添加要监控的Turbine地址,然后进入仪表盘就可以看到统计信息。

  

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值