15.2 声明断路器
15.2 声明断路器
在声明断路器之前,我们需要添加Spring Cloud Netflix Hystrix starter依赖到每个服务的构建文件中。在Maven pom.xml文件中,依赖如下所示:
1 | <dependency> |
作为Spring Cloud套件的一部分,我们需要在构建文件中声明Spring Cloudrelease train的依赖管理。在我编写本书的时候,最新的release train版本为Finchley.SR1。所以,应该将Spring Cloud的版本设置为一个属性,如下的条目应该出现在pom.xml文件的<dependencyManagement>
代码块中:
1 | <properties> |
Hystrix starter就绪之后,接下来的事情就是启用Hystrix。为了实现这一点,我们可以在应用的主配置类上添加@EnableHystrix
。例如,为了在配料服务上启用Hystrix,我们可以按照如下的方式为IngredientServiceApplication添加注解:
1 |
|
这样,在我们的应用中就启用Hystrix了,也就意味着声明断路器的所有准备工作都做完了。在我们的代码中还没有声明任何一个断路器,这时@HystrixCommand
注解就能够发挥作用了。
任何使用@HystrixCommand
注解的方法都会为其声明一个断路器切面。例如,如下的方法使用支持负载均衡的RestTemplate从配料服务中获取一个Ingredient对象的列表:
1 | public Iterable<Ingredient> getAllIngredients() { |
对exchange()的调用可能会遇到问题。如果Eureka没有注册名为ingredient-service的服务或者由于某种原因请求失败了,那么将会抛出RestClientException(非检查型异常)。因为异常没有在try/catch代码块中进行处理,所以调用者必须要处理这个异常。如果调用者不处理,那么它将沿着调用栈往上抛出;如果它根本没有得到处理,那么这个错误会级联到所有上游微服务或客户端。
在任何应用中,未捕获的异常都是一项艰巨的挑战,在微服务中尤为如此。当遇到失败的时候,微服务应该应用维加斯规则(Vegas Rule):在微服务中发生的事情,就留在微服务中。在getAllIngredients()方法上声明断路器将会满足该规则。
按照最少的要求,我们只需要为该方法添加@HystrixCommand
注解并为其提供一个后备方法即可。首先,我们添加@HystrixCommand
注解到getAllIngredients()方法上:
1 |
|
断路器为getAllIngredients()提供了失败防护,所以在遇到失败时它是安全的。如果由于某种原因getAllIngredients()抛出了未捕获的异常,那么断路器将会捕获它们并将方法调用重定向到名为getDefaultIngredients()的方法上。
你可以让后备方法做任何事情,但是它们的本意是当原始的方法无法履行职责时提供后备行为。后备行为方法的唯一规则是它们要与原始方法具有相同的签名(除了方法名称之外)。
为了满足该要求,getAllIngredients()也不能接受任何参数并要返回List<Ingredient>
。如下的getAllIngredients()实现满足该规则,并且返回一个默认的配料列表:
1 | private Iterable<Ingredient> getDefaultIngredients() { |
现在,如果因为某种原因导致getAllIngredients()失败,那么断路器将会调用备用的getDefaultIngredients(),调用者将会接收到默认的配料列表(尽管非常有限)。
你可能会想,如果备用方法本身有断路器又会怎样呢。尽管按照我们的写法,getDefaultIngredients()几乎不可能会出问题,但是更有意思的是getDefaultIngredients()可能会有潜在的失败点。如果是这样,那么我们可以在getDefaultIngredients()上添加@HystrixCommand
注解并提供另一个备用方法。实际上,需要的话,我们可以堆积任意数量的备用方法。唯一的要求就是必须要在后备方法的底部有一个不会失败的方法,该方法不需要使用断路器。
15.2.1 缓解延迟
断路器还能缓解延迟。如果某个方法需要较长的时间才能返回,断路器会将它设置为超时。默认情况下,所有带有@HystrixCommand注解的方法都会在1秒之后超时,并调用它们所声明的后备方法。这意味着,如果因为某种原因配料服务响应缓慢,那么对getAllIngredients()调用会在1秒之后超时,而且会调用getDefaultIngredients()作为替代方案。
1秒超时是一个合理的默认值,适用于大多数的场景。我们也可以通过Hystrix命令属性将其调整为更大或更小的限制值。设置Hystrix命令属性可以通过@HystrixCommand注解的commandProperties属性来实现。commandProperties属性是一个或多个@HystrixProperty注解所组成的数组,指定了要设置的属性名和值1。
为了调整断路器的超时值,我们需要设置Hystrix命令属性execution.isolation.thread.timeoutInMilliseconds。例如,为了将getAllIngredients()的超时时间更加严格地设置为0.5秒,那么我们可以将超时设置为500,如下所示:
1 |
|
这里设置的值是毫秒数。如果我们希望放松限制,那么可以将其设置成一个更大的值。或者,如果你认为这里不应该使用超时功能,那么可以将execution.timeout.enabled属性设置为false,直接将超时功能移除:
1 |
|
将execution.timeout.enabled为false的话就没有延迟防护了。在本例中,getAllIngredients()方法不管是耗用1秒、10秒还是30分钟,它都不会超时。这可能会导致级联的延迟效果,所以在禁用执行超时的时候要非常小心。
15.2.2 管理断路器的阈值
默认情况下,如果断路器保护的方法调用超过20次,而且50%以上的调用在10秒的时间内发生失败,那么断路器就会进入打开状态。所有后续的调用都将会由后备方法处理。在5秒之后,断路器进入半开状态,将会再次尝试调用原始的方法。
我们可以通过设置Hystrix命令属性调整失败和重试的阈值。如下的命令属性将会影响断路器的行为。
- circuitBreaker.requestVolumeThreshold:在给定的时间范围内,方法应该被调用的次数。
- circuitBreaker.errorThresholdPercentage:在给定的时间范围内,方法调用产生失败的百分比。
- metrics.rollingStats.timeInMilliseconds:控制请求量和错误百分比的滚动时间周期。
- circuitBreaker.sleepWindowInMilliseconds:处于打开状态的断路器要经过多长时间才会进入半开状态,进入半开状态之后,将会再次尝试失败的原始方法。
如果在metrics.rollingState.timeInMilliseconds设定的时间范围内超出了circuitBreaker.requestVolumeThreshold和circuitBreaker.errorThresholdPercentage设置的值,那么断路器将会进入打开状态。在circuitBreaker.sleepWindowInMilliseconds限定的时间范围内,它会一直处于打开状态,在此之后将进入半开状态,进入半开状态之后,将会再次尝试失败的原始方法。
例如,我们调整失败的设置:将其变更为在20秒的时间范围内调用超过30次且失败率超过25%。为了实现这一点,我们需要按照如下的方式调整Hystrix命令属性:
1 |
|
另外,我们还决定处于打开状态之后断路器必须保持1分钟,然后才进入半开状态,那么我们还需要设置circuitBreaker.sleepWindowInMilliseconds命令属性:
1 |
除了优雅地处理方法调用失败和延迟之外,Hystrix还为应用中的每个断路器提供了一个指标流。接下来,我们看一下如何通过Hystrix流监控启用Hystrix功能的应用的监控状况。