15.2 定义断路器

在声明断路器之前,您需要为服务添加 Spring Cloud Netflix Hystrix 的依赖。在 Maven pom.xml 中的依赖项如下所示:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

作为 Spring Cloud 组件的一部分,您还需要声明依赖管理。在我写这篇文章的时候,最新的发布版本为 Finchley.SR1。因此,Spring Cloud 的版本应该设置一下,以下条目应出现在 pom.xml 文件的 <dependency-Management> 部分:

<properties>
  ...
  <spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>

...

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

注意,创建项目时在 Initializr 中也可以通过勾选 Hystrix 复选框来添加依赖。如果使用 Initializr 将 Hystrix 添加到项目中,则依赖管理部分会自动为您创建好。

有了 Hystrix 依赖,接下来需要做的就是启用 Hystrix。这通过对每个应用程序的主配置添加 @EnableHystrix 注解来实现。例如,要在 ingredient 服务中启用 Hystrix,您可以这样注解 IngredientServiceApplication 类:

@SpringBootApplication
@EnableHystrix
public class IngredientServiceApplication {
  ...
}

此时,应用程序中启用了 Hystrix,但这只意味着可以声明断路器了。您还没有在任何一个方法上声明使用断路器。这就要通过使用 @HystrixCommand 注解来实现。

任何用 @HystrixCommand 注解的方法都将被声明为具有断路器切面。例如,考虑下面这个方法,使用负载平衡的 RestTemplate 从 ingredient 服务查询 Ingredient 实体:

public Iterable<Ingredient> getAllIngredients() {
  ParameterizedTypeReference<List<Ingredient>> stringList =
    new ParameterizedTypeReference<List<Ingredient>>() {};
  return rest.exchange(
    "http://ingredient-service/ingredients", HttpMethod.GET,
    HttpEntity.EMPTY, stringList).getBody();
}

exchange() 的调用可能会引起问题。如果没有在 Eureka 中注册 ingredient-service,或者由于一些原因请求失败了,那么将抛出 RestClientException 异常(未检查的异常)。因为异常没有使用 try/catch 处理,调用方必须处理这个异常。如果调用方不处理,那么它将继续向上抛出到调用堆栈的上游。如果上游也没有处理,错误就会级联抛出到任何上游微服务或客户端。

未捕获的异常在任何应用程序中都是一个挑战,在微服务中尤其如此。当涉及到失败时,微服务应该遵循维加斯规则——发生在微服务中的,只停留在微服务中。在 getAllIngredients() 方法上声明断路器以满足该规则。

您只需要对方法添加 @HystrixCommand 注解,然后提供一个降级方法。首先,让我们将 @HystrixCommand 添加到 getAllIngredients() 方法上:

@HystrixCommand(fallbackMethod="getDefaultIngredients")
public Iterable<Ingredient> getAllIngredients() {
  ...
}

有一个断路器保护它不发生故障,现在 getAllIngredients() 方法是故障安全的。无论出于何种原因,从 getAllIngredients() 中抛出未捕获的异常,断路器都将捕获它们,并将方法调用重定向到 getDefaultIngredients() 方法。

降级方法可以做任何您想让他们做的事情,但是目的是,在最初使用的方法无法使用时,提供备份行为。降级方法的唯一规则是它要具有与原方法相同的签名(除了方法名)。

要满足此要求, getAllIngredients() 方法必须也没有请求参数,并返回 List<Ingredient>getAllIngredients() 满足该规则并返回默认列表数据:

private Iterable<Ingredient> getDefaultIngredients() {
  List<Ingredient> ingredients = new ArrayList<>();
  ingredients.add(new Ingredient(
        "FLTO", "Flour Tortilla", Ingredient.Type.WRAP));
  ingredients.add(new Ingredient(
        "GRBF", "Ground Beef", Ingredient.Type.PROTEIN));
  ingredients.add(new Ingredient(
        "CHED", "Shredded Cheddar", Ingredient.Type.CHEESE));
return ingredients;
}

现在,如果 getAllIngredients() 方法失败了,断路器将调用 getDefaultIngredients(),调用者将收到一个默认列表。

您可能想知道降级方法本身是否有断路器。尽管上面的 getDefaultIngredients() 实现不会出什么问题,但有可能其他实现会有潜在的失败点。在这种情况下,您可以为 getDefaultIngredients() 添加 @HystrixCommand,并提供另一个降级方法。事实上,如果需要,您可以堆叠尽可能多的降级方法。唯一的限制是在降级的底部,必须有一个方法不发生故障且不再需要断路器的堆栈。

最后更新于