HystrixRPC保护的原理:RPC保护的目标与HystrixCommand简介

RPC保护的目标

在分布式多节点集群架构系统内部,在节点之间进行RPC保护的目标如下:

(1)避免整个系统出现级联失败而雪崩,这是非常重要的目标。

在RPC调用过程中,需要防止由单个服务的故障而耗尽整个服务集群的线程资源,避免分布式环境里大量级联失败。

(2)RPC调用能够相互隔离。

为每一个目标服务维护着一个线程池(或信号量),即使其中某个目标服务的调用资源被耗尽,也不会影响对其他服务的RPC调用。当目标服务的线程池(或信号量)被耗尽时,拒绝RPC调用。

(3)能够快速地降级和恢复。

当RPC目标服务故障时,能够快速和优雅地降级;当RPC目标服务失效后又恢复正常时,快速恢复。

(4)能够对RPC调用提供接近实时的监控和警报。

监控信息包括请求成功、请求失败、请求超时和线程拒绝。如果对特定服务RPC调用的错误百分比超过阈值,后续的RPC调用就会自动失败,一段时间内停止对该服务的所有请求。

前面已经介绍Spring Cloud在调用处理器中是使用HystrixCommand命令封装RPC调用,从而实现RPC保护。

HystrixCommand简介

Hystrix使用命令模式并结合RxJava的响应式编程和滑动窗口技术实现了对外部服务RPC调用的保护。

Hystrix实现了HystrixCommand和HystrixObservableCommand两个命令类,用于封装需要保护的RPC调用。由于其中的HystrixObservableCommand命令不具备同步执行的能力,只具备异步执行能力,而HystrixCommand命令却都具备,并且Spring Cloud中重点使用HystrixCommand命令,因此本章将以HystrixCommand命令为重点介绍Hystrix的原理和使用。

HystrixCommand的使用

如果不是在Spring Cloud的开发环境中使用HystrixCommand命令,就需要增加其Maven的依赖坐标,设置如下:

<dependency>
 <groupId>com.netflix.hystrix</groupId>
 <artifactId>hystrix-core</artifactId>
 </dependency>

独立使用HystrixCommand命令主要有以下两个步骤:

(1)继承HystrixCommand类,将正常的业务逻辑实现在继承的run方法中,将回退的业务逻辑实现在继承的getFallback方法中。

(2)使用HystrixCommand类提供的启动方法启动命令的执行。

HystrixCommand命令的run方法是异步调用(或者同步调用)时被调度时执行的方法,getFallback方法是当run执行异常(或超时等)时的回退方法。

使用HystrixCommand命令时,需要通过它的启动方法(如execute)来启动其执行,这个过程有点像使用Thread时通过start方法启动run方法的执行。

HystrixCommand命令的完整执行过程比较复杂,简化版本的HystrixCommand命令的执行过程如图5-1所示。

HystrixRPC保护的原理:RPC保护的目标与HystrixCommand简介

图5-1 简化版本的HystrixCommand命令的执行过程

下面通过继承HystrixCommand创建一个简单的HTTP请求命令,并且对HTTP请求过程中执行的总次数、失败的总次数进行统计,具体的代码如下:

package com.crazymaker.demo.hystrix;
//省略import
@Slf4j
public class HttpGetterCommand extends HystrixCommand<String>
{
 private String url;
 //run方法是否执行
 private boolean hasRun = false;
 //执行的次序
 private int index;
 //执行的总次数,线程安全
 private static AtomicInteger total = new AtomicInteger(0);
 //失败的总次数,线程安全
 private static AtomicInteger failed = new AtomicInteger(0);
 public HttpGetterCommand(String url, Setter setter)
 {
 super(setter);
 this.url = url;
 }
 @Override
 protected String run() throws Exception
 {
 hasRun = true;
 index = total.incrementAndGet();
 log.info("req{} begin...", index);
 String responseData = HttpRequestUtil.simpleGet(url);
 log.info(" req{} end: {}", index, responseData);
 return "req" + index + ":" + responseData;
 }
 @Override
 protected String getFallback()
 {
 //是否直接失败
 boolean isFastFall = !hasRun;
 if (isFastFall)
 {
 index = total.incrementAndGet();
 }
 if (super.isCircuitBreakerOpen())
 {
 HystrixCommandMetrics.HealthCounts hc =
super.getMetrics().getHealthCounts();
 log.info("window totalRequests:{},errorPercentage:{}",
 hc.getTotalRequests(), //滑动窗口总的请求数
 hc.getErrorPercentage()); //滑动窗口出错比例
 }
 //熔断器是否打开
 boolean isCircuitBreakerOpen = isCircuitBreakerOpen();
 log.info("req{} fallback: 熔断{},直接失败 {},失败次数{}",
 index,
 isCircuitBreakerOpen,
 isFastFall,
 failed.incrementAndGet());
 return "req" + index + ":调用失败";
 }
}

以上自定义的HTTP请求命令HttpGetterCommand继承了HystrixCommand,并且实现了该基类的run和getFallback两个方法。在构造函数中,使用HystrixCommand.Setter配置实例对该基类的实例进行了初始化。

HttpGetterCommand的测试用例代码如下:

package com.crazymaker.demo.hystrix;
...
@Slf4j
public class HystryxCommandExcecuteDemo
{
 /***测试HttpGetterCommand */
 @Test
 public void testHttpGetterCommand() throws Exception
 {
 /**
 *构造配置实例
 */
 HystrixCommand.Setter setter = HystrixCommand.Setter
 .withGroupKey(HystrixCommandGroupKey.Factory.asKey("group-1"))
 .andCommandKey(HystrixCommandKey.Factory.asKey("command-1"))
 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadPool-1"));
 /**测试HttpGetterCommand */
 String result =new HttpGetterCommand(HELLO_TEST_URL, setter)
.execute();
 log.info("result={}", result);
 }
}

用例中首先构造了一个配置实例setter,配置了非常基础的命令组Key(GroupKey)、命令Key(CommandKey)、线程池Key(ThreadPoolKey)3个配置项,然后创建了HttpGetterCommand实例并使用execute()执行该命令,执行的结果大致如下:

[hystrix-testThreadPool-1] INFO c.c.d.h.HttpGetterCommand - req1 begin...
[hystrix-testThreadPool-1] INFO c.c.d.h.HttpGetterCommand - req1 fallback: 熔断false,直接失败false,失败次数 1
[main] INFO c.c.d.h.HystryxCommandExcecuteDemo - result=req1:调用失败
这里的HttpGetterCommand实例所请求的地址是一个常量,其值如下:
 /**
 *演示用地址: demo-provider的REST接口 /api/demo/hello/v1
 *根据实际的地址调整
 */
 public static final String HELLO_TEST_URL =
 "http://crazydemo.com:7700/demo-provider/api/demo/hello/v1";

为了演示启动请求失败的过程,这里特意没有启动demo-provider服务,所以从上面的执行结果中可以看到,由于HTTP请求失败,因此getFallback()回退方法被成功地执行了。

HystrixCommand的配置内容和方式

HystrixCommand命令的配置方式之一是使用HystrixCommand.Setter配置实例进行配置,简单的配置实例如下:

 HystrixCommand.Setter setter = HystrixCommand.Setter
 .withGroupKey(HystrixCommandGroupKey.Factory.asKey("group-1"))
 .andCommandKey(HystrixCommandKey.Factory.asKey("command-1"))
 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadPool-1"));

其中涉及以下3个配置项:

(1)CommandKey:该命令的名称。

(2)GroupKey:该命令属于哪一个组,以帮助我们更好地组织命令。

(3)ThreadPoolKey:该命令所属线程池的名称,相同的线程池名称会共享同一线程池,若不进行配置,则默认使用GroupKey作为线程池名称。

除此之外,还可以通过HystrixCommand.Setter配置实例,整体设置一些其他的属性集合,如:

(1)CommandProperties:与命令执行相关的一些属性集,包括降级设置、熔断器的配置、隔离策略以及一些监控指标配置项等。

(2)ThreadPoolProperties:与线程池相关的一些属性集,包括线程池大小、排队队列的大小等。由于本书的很多用例要用到HystrixCommand.Setter配置实例,因此专门写了一个方法获取配置实例,它的源码如下:

package com.crazymaker.demo.hystrix;
...
@Slf4j
public class SetterDemo
{
 public static HystrixCommand.Setter buildSetter(
 String groupKey,
 String commandKey,
 String threadPoolKey)
 {
 /**
 *与命令执行相关的一些属性集
 */
 HystrixCommandProperties.Setter commandSetter =
 HystrixCommandProperties.Setter()
 //至少有3个请求,熔断器才达到熔断触发的次数阈值
 .withCircuitBreakerRequestVolumeThreshold(3)
 //熔断器中断请求5秒后会进入half-open状态,尝试放行
 .withCircuitBreakerSleepWindowInMilliseconds(5000)
 //错误率超过60%,快速失败
 .withCircuitBreakerErrorThresholdPercentage(60)
 //启用超时
 .withExecutionTimeoutEnabled(true)
 //执行的超时时间,默认为1000ms
 .withExecutionTimeoutInMilliseconds(5000)
 //可统计的滑动窗口内的buckets数量,用于熔断器和指标发布
 .withMetricsRollingStatisticalWindowBuckets(10)
 //可统计的滑动窗口的时间长度
 //这段时间内的执行数据用于熔断器和指标发布
 .withMetricsRollingStatisticalWindowInMilliseconds(10000);
 /**
 *线程池配置
 */
 HystrixThreadPoolProperties.Setter poolSetter =
 HystrixThreadPoolProperties.Setter()
 //这里我们设置了线程池大小为5
 .withCoreSize(5)
 .withMaximumSize(5);
 /**
 *与线程池相关的一些属性集
 */
 HystrixCommandGroupKey hGroupKey = HystrixCommandGroupKey.Factory.
asKey(groupKey);
 HystrixCommandKey hCommondKey = HystrixCommandKey.Factory.
asKey(commandKey);
 HystrixThreadPoolKey hThreadPoolKey = HystrixThreadPoolKey.Factory.
asKey(threadPoolKey);
 HystrixCommand.Setter outerSetter = HystrixCommand.Setter
 .withGroupKey(hGroupKey)
 .andCommandKey(hCommondKey)
 .andThreadPoolKey(hThreadPoolKey)
 .andCommandPropertiesDefaults(commandSetter)
 .andThreadPoolPropertiesDefaults(poolSetter);
 return outerSetter;
 }
}

以上代码中涉及的配置项比较多,后面都会介绍。

HystrixCommand命令的配置方式之二是使用Hystrix提供的ConfigurationManager配置管理类的工厂实例对HystrixCommand命令的执行参数进行配置。下面是一个简单的实例:

 //熔断器的请求次数阈值:大于3次请求
 ConfigurationManager
 .getConfigInstance()
 .setProperty("hystrix.command.default.circuitBreaker.
requestVolumeThreshold", 3);

Spring Cloud Hystrix所使用的正是这种配置方法。

本文给大家讲解的内容是SpringCloudRPC远程调用核心原理: Hystrix RPC保护的原理 RPC保护的目标与HystrixCommand简介

  1. 下篇文章给大家讲解的是SpringCloudRPC远程调用核心原理:Hystrix RPC保护的原理,HystrixCommand命令的执行方法;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

 

上一篇:老调重弹:JDBC系列 之


下一篇:.NET中的视图和过滤器 (DefaultView和RowFilter)