A Guide to Spring Cloud Netflix – Hystrix – Spring Cloud Netflix指南 – Hystrix

最后修改: 2016年 9月 2日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll cover Spring Cloud Netflix Hystrix – the fault tolerance library. We’ll use the library and implement the Circuit Breaker enterprise pattern, which is describing a strategy against failure cascading at different levels in an application.

在本教程中,我们将介绍Spring Cloud Netflix Hystrix–容错库。我们将使用该库并实现Circuit Breaker企业模式,该模式描述的是针对应用程序中不同级别的故障级联的策略。

The principle is analogous to electronics: Hystrix is watching methods for failing calls to related services. If there is such a failure, it will open the circuit and forward the call to a fallback method.

其原理类似于电子学。Hystrix正在观察调用相关服务失败的方法。如果有这样的故障,它将打开电路,并将调用转发到一个后备方法。

The library will tolerate failures up to a threshold. Beyond that, it leaves the circuit open. Which means, it will forward all subsequent calls to the fallback method, to prevent future failures. This creates a time buffer for the related service to recover from its failing state.

该库将容忍失败,直到一个阈值。超过这个阈值,它就会让电路开放。这意味着,它将把所有后续调用转发到回退方法,以防止未来出现故障。这为相关服务创造了一个时间缓冲区,以便从其故障状态中恢复。

2. REST Producer

2.REST生产者

To create a scenario, which demonstrates the Circuit Breaker pattern, we need a service first. We’ll name it “REST Producer” since it provides data for the Hystrix-enabled “REST Consumer”, which we’ll create in the next step.

为了创建一个演示断路器模式的场景,我们首先需要一个服务。我们将它命名为 “REST生产者”,因为它为支持Hystrix的 “REST消费者 “提供数据,我们将在下一步创建。

Let’s create a new Maven project using the spring-boot-starter-web dependency:

让我们使用spring-boot-starter-web依赖项创建一个新的Maven项目。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

The project itself is intentionally kept simple. It consists of a controller interface with one @RequestMapping annotated GET method returning simply a String, a @RestController implementing this interface and a @SpringBootApplication.

该项目本身有意保持简单。它由一个控制器接口和一个@RequestMapping注解的GET方法组成,该方法只返回一个字符串,@RestController实现这个接口和一个@SpringBootApplication

We’ll begin with the interface:

我们将从界面开始。

public interface GreetingController {
    @GetMapping("/greeting/{username}")
    String greeting(@PathVariable("username") String username);
}

And the implementation:

而实施。

@RestController
public class GreetingControllerImpl implements GreetingController {
 
    @Override
    public String greeting(@PathVariable("username") String username) {
        return String.format("Hello %s!\n", username);
    }
}

Next, we’ll write down the main application class:

接下来,我们将写下主应用程序类。

@SpringBootApplication
public class RestProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RestProducerApplication.class, args);
    }
}

To complete this section, the only thing left to do is to configure an application-port on which we’ll be listening. We won’t use the default port 8080 because the port should remain reserved for the application described in the next step.

要完成这一部分,唯一要做的是配置一个我们要监听的应用端口。我们将不使用默认的8080端口,因为该端口应保留给下一步描述的应用程序。

Furthermore, we’re defining an application name to be able to look-up our producer from the client application that we’ll introduce later.

此外,我们还定义了一个应用程序的名称,以便能够从我们稍后介绍的客户端应用程序中查找我们的生产者。

Let’s then specify a port of 9090 and a name of rest-producer in our application.properties file:

然后让我们在application.properties文件中指定一个9090的端口和一个rest-producer的名字。

server.port=9090
spring.application.name=rest-producer

Now we’re able to test our producer using cURL:

现在我们能够使用cURL来测试我们的制作人。

$> curl http://localhost:9090/greeting/Cid
Hello Cid!

3. REST Consumer With Hystrix

3.使用Hystrix的REST消费者

For our demonstration scenario, we’ll be implementing a web application, which is consuming the REST service from the previous step using RestTemplate and Hystrix. For the sake of simplicity, we’ll call it the “REST Consumer”.

在我们的示范方案中,我们将实现一个Web应用程序,它使用RestTemplateHystrix消费上一步的REST服务。为了简单起见,我们将称它为 “REST消费者”。

Consequently, we create a new Maven project with spring-cloud-starter-hystrix, spring-boot-starter-web and spring-boot-starter-thymeleaf as dependencies:

因此,我们创建了一个新的Maven项目,将spring-cloud-starter-hystrixspring-boot-starter-webspring-boot-starter-thymeleaf作为依赖项。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

For the Circuit Breaker to work, Hystix will scan @Component or @Service annotated classes for @HystixCommand annotated methods, implement a proxy for it and monitor its calls.

为了让断路器工作,Hystix将扫描@Component@Service注解类中的@HystixCommand注解方法,为其实现一个代理并监控其调用。

We’re going to create a @Service class first, which will be injected to a @Controller. Since we’re building a web application using Thymeleaf, we also need an HTML template to serve as a view.

我们将首先创建一个@Service类,它将被注入到@Controller。由于我们要使用Thymeleaf构建一个网络应用,我们还需要一个HTML模板来作为视图。

This will be our injectable @Service implementing a @HystrixCommand with an associated fallback method. This fallback has to use the same signature as the original:

这将是我们可注入的@Service,实现一个@HystrixCommand,有一个相关的回退方法。这个回退方法必须使用与原始方法相同的签名。

@Service
public class GreetingService {
    @HystrixCommand(fallbackMethod = "defaultGreeting")
    public String getGreeting(String username) {
        return new RestTemplate()
          .getForObject("http://localhost:9090/greeting/{username}", 
          String.class, username);
    }
 
    private String defaultGreeting(String username) {
        return "Hello User!";
    }
}

RestConsumerApplication will be our main application class. The @EnableCircuitBreaker annotation will scan the classpath for any compatible Circuit Breaker implementation.

RestConsumerApplication将是我们的主应用类。@EnableCircuitBreaker注解将扫描classpath以寻找任何兼容的Circuit Breaker实现。

To use Hystrix explicitly, we have to annotate this class with @EnableHystrix:

为了明确地使用Hystrix,我们必须用@EnableHystrix来注释这个类。

@SpringBootApplication
@EnableCircuitBreaker
public class RestConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RestConsumerApplication.class, args);
    }
}

We’ll set up the controller using our GreetingService:

我们将使用我们的GreetingService设置控制器。

@Controller
public class GreetingController {
 
    @Autowired
    private GreetingService greetingService;
 
    @GetMapping("/get-greeting/{username}")
    public String getGreeting(Model model, @PathVariable("username") String username) {
        model.addAttribute("greeting", greetingService.getGreeting(username));
        return "greeting-view";
    }
}

And here’s the HTML template:

这里是HTML模板。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Greetings from Hystrix</title>
    </head>
    <body>
        <h2 th:text="${greeting}"/>
    </body>
</html>

To ensure that the application is listening on a defined port, we put the following in an application.properties file:

为了确保应用程序在定义的端口上进行监听,我们在application.properties文件中放入以下内容。

server.port=8080

To see a Hystix circuit breaker in action, we’re starting our consumer and pointing our browser to http://localhost:8080/get-greeting/Cid. Under normal circumstances, the following will be shown:

为了看到Hystix断路器的运行情况,我们要启动消费者,将浏览器指向http://localhost:8080/get-greeting/Cid。在正常情况下,将显示以下内容。

Hello Cid!

To simulate a failure of our producer, we’ll simply stop it, and after we finished refreshing the browser we should see a generic message, returned from the fallback method in our @Service:

为了模拟生产者的失败,我们将简单地停止它,在我们完成刷新浏览器后,我们应该看到一个通用的消息,从我们的@Service中的回退方法返回。

Hello User!

4. REST Consumer With Hystrix and Feign

4.使用Hystrix和Feign的REST消费者

Now, we’re going to modify the project from the previous step to use Spring Netflix Feign as declarative REST client, instead of Spring RestTemplate.

现在,我们要修改上一步的项目,使用Spring Netflix Feign作为声明式REST客户端,而不是Spring RestTemplate

The advantage is that we’re later able to easily refactor our Feign Client interface to use Spring Netflix Eureka for service discovery.

这样做的好处是,我们后来能够轻松地重构我们的Feign客户端接口,以使用Spring Netflix Eureka进行服务发现。

To start the new project, we’ll make a copy of our consumer, and add our producer and spring-cloud-starter-feign as dependencies:

为了启动新项目,我们将制作一个消费者的副本,并将我们的生产者和spring-cloud-starter-feign作为依赖项。

<dependency>
    <groupId>com.baeldung.spring.cloud</groupId>
    <artifactId>spring-cloud-hystrix-rest-producer</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.1.5.RELEASE</version>
</dependency>

Now, we’re able to use our GreetingController to extend a Feign Client. We’ll implement Hystrix fallback as a static inner class annotated with @Component.

现在,我们能够使用我们的GreetingController来扩展一个Feign Client。我们将实现Hystrix回退,作为一个用@Component注释的静态内类。

Alternatively, we could define a @Bean annotated method returning an instance of this fallback class.

另外,我们可以定义一个@Bean注释的方法,返回这个回避类的实例。

The name property of the @FeignClient is mandatory. It is used, to look-up the application either by service discovery via a Eureka Client or by URL, if this property is given:

@FeignClient的name属性是强制性的。它被用来通过Eureka客户端的服务发现或通过URL查找应用程序,如果该属性被赋予的话。

@FeignClient(
  name = "rest-producer"
  url = "http://localhost:9090", 
  fallback = GreetingClient.GreetingClientFallback.class
)
public interface GreetingClient extends GreetingController {
     
    @Component
    public static class GreetingClientFallback implements GreetingController {
 
        @Override
        public String greeting(@PathVariable("username") String username) {
            return "Hello User!";
        }
    }
}

For more on using Spring Netflix Eureka for service discovery have a look at this article.

有关使用Spring Netflix Eureka进行服务发现的更多信息,请看这篇文章

In the RestConsumerFeignApplication, we’ll put an additional annotation to enable Feign integration, in fact, @EnableFeignClients, to the main application class:

RestConsumerFeignApplication中,我们将在主应用程序类中加入一个额外的注解来启用Feign集成,事实上,@EnableFeignClients

@SpringBootApplication
@EnableCircuitBreaker
@EnableFeignClients
public class RestConsumerFeignApplication {
     
    public static void main(String[] args) {
        SpringApplication.run(RestConsumerFeignApplication.class, args);
    }
}

We’re going to modify the controller to use an auto-wired Feign Client, rather than the previously injected @Service, to retrieve our greeting:

我们将修改控制器,使用一个自动连接的Feign客户端,而不是之前注入的@Service,来获取我们的问候语。

@Controller
public class GreetingController {
    @Autowired
    private GreetingClient greetingClient;
 
    @GetMapping("/get-greeting/{username}")
    public String getGreeting(Model model, @PathVariable("username") String username) {
        model.addAttribute("greeting", greetingClient.greeting(username));
        return "greeting-view";
    }
}

To distinguish this example from the previous, we’ll alter the application listening port in the application.properties:

为了区分这个例子和前面的例子,我们将改变application.properties中的应用程序监听端口。

server.port=8082

Finally, we’ll test this Feign-enabled consumer like the one from the previous section. The expected result should be the same.

最后,我们将测试这个支持Feign的消费者,就像上一节中的那个消费者。预期的结果应该是一样的。

5. Cache Fallback With Hystrix

5.使用Hystrix的缓存回溯

Now, we are going to add Hystrix to our Spring Cloud project. In this cloud project, we have a rating service that talks to the database and gets ratings of books.

现在,我们要将Hystrix添加到我们的Spring Cloud项目。在这个云项目中,我们有一个评级服务,它与数据库对话并获得书籍的评级。

Let’s assume that our database is a resource under demand, and its response latency might vary in time or might not be available in times. We’ll handle this scenario with the Hystrix Circuit Breaker falling back to a cache for the data.

让我们假设我们的数据库是一个有需求的资源,它的响应延迟可能在时间上有所不同,或者可能在时间上不可用。我们将用Hystrix Circuit Breaker回落到缓存中的数据来处理这种情况。

5.1. Setup and Configuration

5.1.设置和配置

Let us add the spring-cloud-starter-hystrix dependency to our rating module:

让我们把spring-cloud-starter-hystrix依赖性添加到我们的评级模块。

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

When ratings are inserted/updated/deleted in the database, we’ll replicate the same to the Redis cache with a Repository. To learn more about Redis, check this article.

当数据库中插入/更新/删除评级时,我们将通过Repository将其复制到Redis缓存中。要了解更多关于Redis的信息,请查看这篇文章

Let’s update the RatingService to wrap the database querying methods in a Hystrix command with @HystrixCommand and configure it with a fallback to reading from Redis:

让我们更新RatingService,用@HystrixCommand将数据库查询方法包裹在Hystrix命令中,并将其配置为从Redis读取。

@HystrixCommand(
  commandKey = "ratingsByIdFromDB", 
  fallbackMethod = "findCachedRatingById", 
  ignoreExceptions = { RatingNotFoundException.class })
public Rating findRatingById(Long ratingId) {
    return Optional.ofNullable(ratingRepository.findOne(ratingId))
      .orElseThrow(() -> 
        new RatingNotFoundException("Rating not found. ID: " + ratingId));
}

public Rating findCachedRatingById(Long ratingId) {
    return cacheRepository.findCachedRatingById(ratingId);
}

Note that the fallback method should have the same signature of a wrapped method and must reside in the same class. Now when the findRatingById fails or gets delayed more than a given threshold, Hystrix fallbacks to findCachedRatingById.

需要注意的是,回退方法应该具有与封装方法相同的签名,并且必须驻留在同一个类中。现在,当findRatingById失败或者延迟超过某个阈值时,Hystrix会回退到findCachedRatingById

As the Hystrix capabilities are transparently injected as AOP advice, we have to adjust the order in which the advice is stacked, in case if we have other advice like Spring’s transactional advice. Here we have adjusted the Spring’s transaction AOP advice to have lower precedence than Hystrix AOP advice:

由于Hystrix的能力是作为AOP建议透明地注入的,我们必须调整建议的堆叠顺序,如果我们有其他建议,如Spring的事务建议。在这里,我们将Spring的交易AOP建议调整为比Hystrix AOP建议的优先级低。

@EnableHystrix
@EnableTransactionManagement(
  order=Ordered.LOWEST_PRECEDENCE, 
  mode=AdviceMode.ASPECTJ)
public class RatingServiceApplication {
    @Bean
    @Primary
    @Order(value=Ordered.HIGHEST_PRECEDENCE)
    public HystrixCommandAspect hystrixAspect() {
        return new HystrixCommandAspect();
    }
 
    // other beans, configurations
}

Here, we have adjusted the Spring’s transaction AOP advice to have lower precedence than Hystrix AOP advice.

在这里,我们将Spring的交易AOP建议调整为比Hystrix AOP建议的优先级低。

5.2. Testing Hystrix Fallback

5.2.测试Hystrix Fallback

Now that we have configured the circuit, we can test it by bringing down the H2 database our repository interacts with. But first, let’s run the H2 instance as an external process instead of running it as an embedded database.

现在我们已经配置好了电路,我们可以通过关闭与我们的资源库交互的H2数据库来测试它。但首先,让我们把H2实例作为一个外部进程运行,而不是作为一个嵌入式数据库运行。

Let’s copy the H2 library (h2-1.4.193.jar) to a known directory and start the H2 server:

让我们把H2库(h2-1.4.193.jar)复制到一个已知的目录,并启动H2服务器。

>java -cp h2-1.4.193.jar org.h2.tools.Server -tcp
TCP server running at tcp://192.168.99.1:9092 (only local connections)

Let’s now update our module’s data source URL in rating-service.properties to point to this H2 server:

现在让我们在rating-service.properties中更新我们模块的数据源URL,以指向这个H2服务器。

spring.datasource.url = jdbc:h2:tcp://localhost/~/ratings

We can start our services as given in our previous article from the Spring Cloud series, and test ratings of each book by bringing down the external H2 instance we are running.

我们可以按照之前Spring Cloud系列文章中给出的方法启动我们的服务,并通过关闭我们正在运行的外部H2实例来测试每本书的评分。

We could see that when the H2 database is not reachable, Hystrix automatically falls back to Redis to read the ratings for each book. The source code demonstrating this use case can be found here.

我们可以看到,当H2数据库无法到达时,Hystrix会自动回落到Redis来读取每本书的评分。演示这个用例的源代码可以在这里找到。

6. Using Scopes

6.使用作用域

Normally a @HytrixCommand annotated method is executed in a thread pool context. But sometimes it needs to be running in a local scope, for example, a @SessionScope or a @RequestScope. This can be done via giving arguments to the command annotation:

通常,@HytrixCommand注释的方法是在线程池上下文中执行的。但有时它需要在本地范围内运行,例如,@SessionScope@RequestScope。这可以通过给命令注解提供参数来实现。

@HystrixCommand(fallbackMethod = "getSomeDefault", commandProperties = {
  @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")
})

7. The Hystrix Dashboard

7.Hystrix仪表板

A nice optional feature of Hystrix is the ability to monitor its status on a dashboard.

Hystrix的一个不错的可选功能是能够在仪表板上监测其状态。

To enable it, we’ll put spring-cloud-starter-hystrix-dashboard and spring-boot-starter-actuator in the pom.xml of our consumer:

为了启用它,我们将把spring-cloud-starter-hystrix-dashboard spring-boot-starter-actuator 放入我们消费者的pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

The former needs to be enabled via annotating a @Configuration with @EnableHystrixDashboard and the latter automatically enables the required metrics within our web application.

前者需要通过注释@Configuration@EnableHystrixDashboard来启用,后者在我们的网络应用中自动启用所需的指标。

After we’ve done restarting the application, we’ll point a browser at http://localhost:8080/hystrix, input the metrics URL of a Hystrix stream and begin monitoring.

在我们完成重启应用程序后,我们将把浏览器指向http://localhost:8080/hystrix,输入Hystrix流的指标URL并开始监测。

Finally, we should see something like this:

最后,我们应该看到这样的东西。

Screenshot_20160819_031730

Monitoring a Hystrix stream is something fine, but if we have to watch multiple Hystrix-enabled applications, it will become inconvenient. For this purpose, Spring Cloud provides a tool called Turbine, which can aggregate streams to present in one Hystrix dashboard.

监控一个Hystrix流是很好的事情,但如果我们必须观看多个支持Hystrix的应用程序,就会变得很不方便。为此,Spring Cloud提供了一个名为Turbine的工具,它可以将数据流聚合到一个Hystrix仪表盘中。

Configuring Turbine is beyond the scope of this write-up, but the possibility should be mentioned here. So it’s also possible to collect these streams via messaging, using Turbine stream.

配置Turbine已经超出了本篇文章的范围,但这里应该提到这种可能性。因此,也可以通过消息传递来收集这些流,使用Turbine流。

8. Conclusion

8.结论

As we’ve seen so far, we’re now able to implement the Circuit Breaker pattern using Spring Netflix Hystrix together with either Spring RestTemplate or Spring Netflix Feign.

正如我们到目前为止所看到的,我们现在能够使用Spring Netflix Hystrix与Spring RestTemplate或Spring Netflix Feign一起实现断路器模式。

This means that we’re able to consume services with included fallback using default data, and we’re able to monitor the usage of this data.

这意味着我们能够使用默认数据消费包含回退的服务,并且我们能够监控这些数据的使用。

As usual, we can find the sources on GitHub.

像往常一样,我们可以在GitHub上找到源代码。