1. 概述
在本教程中,我们将介绍Spring Cloud Netflix Hystrix-容错库。我们将使用该库并实施断路器企业模式,该模式描述了一种针对应用程序中不同级别的故障级联的策略。
其原理类似于电子设备:Hystrix正在监视对相关服务调用失败的方法。如果出现此类故障,它将打开电路并将调用转发给回退方法。
该库将容忍不超过阈值的故障。除此之外,它使电路保持打开状态。这意味着,它将所有后续调用转发给回退方法,以防止将来发生故障。这为相关服务从其失败状态中恢复创建了一个时间缓冲区。
2. REST生产者
要创建一个演示断路器模式的场景,我们首先需要一个服务。我们将其命名为“REST Producer”,因为它为支持Hystrix的“REST Consumer”提供数据,我们将在下一步中创建该消费者。
让我们使用spring-boot-starter-web依赖创建一个新的Maven项目:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
该项目本身有意保持简单。它由一个控制器接口和一个带有@RequestMapping注解的GET方法(该方法只返回一个字符串)组成、一个实现此接口的@RestController和一个@SpringBootApplication组成。
我们将从接口开始:
public interface GreetingController {
@GetMapping("/greeting/{username}")
String greeting(@PathVariable("username") String username);
}
和实现:
@RestController
public class GreetingControllerImpl implements GreetingController {
@Override
public String greeting(@PathVariable("username") String username) {
return String.format("Hello %s!\n", username);
}
}
接下来,创建我们的主应用程序类:
@SpringBootApplication
public class RestProducerApplication {
public static void main(String[] args) {
SpringApplication.run(RestProducerApplication.class, args);
}
}
要完成本节,剩下要做的唯一一件事就是配置我们将在其上监听的应用程序端口。我们不会使用默认端口8080,因为该端口应保留用于下一步中所述的应用程序。
此外,我们正在定义一个应用程序名称,以便能够从稍后介绍的客户端应用程序中查找我们的生产者。
然后让我们在application.properties文件中指定端口9090和rest-producer的名称:
server.port=9090
spring.application.name=rest-producer
现在我们可以使用cURL测试我们的生产者:
$> curl http://localhost:9090/greeting/Cid
Hello Cid!
3. 使用Hystrix的REST消费者
对于我们的演示场景,我们将实现一个Web应用程序,该应用程序使用RestTemplate和Hystrix消费上一步中的REST服务。为了简单起见,我们将其称为“REST Consumer”。
因此,我们创建一个新的Maven项目,其中包含spring-cloud-starter-hystrix、spring-boot-starter-web和spring-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>
为了使断路器工作,Hystrix将扫描@Component或@Service注解类以查找@HystixCommand注解方法,为其实现代理并监视其调用。
我们将首先创建一个@Service类,该类将被注入到@Controller中。由于我们正在使用Thymeleaf构建Web应用程序,因此我们还需要一个HTML模板作为视图。
这将是我们的可注入@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将是我们的主应用程序类。@EnableCircuitBreaker注解将扫描类路径以查找任何兼容的断路器实现。
要显式使用Hystrix,我们必须使用@EnableHystrix标注这个类:
@SpringBootApplication
@EnableCircuitBreaker
public class RestConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RestConsumerApplication.class, args);
}
}
我们将使用我们的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";
}
}
这是HTML模板:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Greetings from Hystrix</title>
</head>
<body>
<h2 th:text="${greeting}"/>
</body>
</html>
为了确保应用程序正在监听定义的端口,我们将以下内容放入application.properties文件中:
server.port=8080
为了看到Hystrix断路器的实际运行情况,我们启动消费者并将浏览器指向http://localhost:8080/get-greeting/Cid。一般情况下,会显示以下内容:
Hello Cid!
为了模拟我们的生产者的失败,我们只需简单地停止它,并且在我们刷新浏览器之后我们应该看到一条通用消息,从我们的@Service中的回退方法返回:
Hello User!
4. 使用Hystrix和Feign的REST消费者
现在,我们将修改上一步中的项目,以使用Spring Netflix Feign作为声明式REST客户端,而不是Spring RestTemplate。
优点是我们以后可以轻松重构我们的Feign Client接口,以使用Spring Netflix Eureka进行服务发现。
为了启动新项目,我们将复制我们的消费者,并将我们的生产者和spring-cloud-starter-feign添加为依赖项:
<dependency>
<groupId>cn.tuyucheng.taketoday.spring.cloud</groupId>
<artifactId>spring-cloud-hystrix-rest-producer</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
现在,我们可以使用我们的GreetingController来扩展一个Feign Client。我们将Hystrix回退实现为一个用@Component标注的静态内部类。
或者,我们可以定义一个带@Bean注解的方法,返回此回退类的实例。
@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!";
}
}
}
有关使用Spring Netflix Eureka进行服务发现的更多信息,请查看本文。
在RestConsumerFeignApplication中,我们将添加一个额外的注解来启用Feign集成,实际上是将@EnableFeignClients添加到主应用程序类:
@SpringBootApplication
@EnableCircuitBreaker
@EnableFeignClients
public class RestConsumerFeignApplication {
public static void main(String[] args) {
SpringApplication.run(RestConsumerFeignApplication.class, args);
}
}
我们将修改控制器以使用自动注入的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";
}
}
为了将此示例与之前的示例区分开来,我们将更改application.properties中的应用程序监听端口:
server.port=8082
最后,我们将像上一节中的那样测试这个支持Feign的消费者。预期的结果应该是一样的。
5. 使用Hystrix缓存回退
现在,我们要将Hystrix添加到我们的Spring Cloud项目中。在这个云项目中,我们有一个与数据库对话并获得书籍评级的评级服务。
让我们假设我们的数据库是一种需求不足的资源,它的响应延迟可能会随时间变化或可能有时不可用。我们将使用Hystrix断路器回退到数据缓存来处理这种情况。
5.1 设置和配置
让我们将spring-cloud-starter-hystrix依赖项添加到我们的评级模块中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
当在数据库中插入/更新/删除评级时,我们将使用Repository将其到复制Redis缓存中。要了解有关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);
}
请注意,回退方法应具有与包装方法相同的签名,并且必须位于同一类中。现在,当findRatingById失败或延迟超过给定阈值时,Hystrix会回退到findCachedRatingById。
由于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
}
5.2 测试Hystrix回退
现在我们已经配置了电路,我们可以通过关闭与我们的Repository交互的H2数据库来测试它。但首先,让我们将H2实例作为外部进程运行,而不是将其作为嵌入式数据库运行。
让我们将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)
现在让我们在rating-service.properties中更新模块的数据源URL以指向此H2服务器:
spring.datasource.url = jdbc:h2:tcp://localhost/~/ratings
我们可以启动我们之前在Spring Cloud系列文章中给出的服务,并通过关闭我们正在运行的外部H2实例来测试每本书的评级。
我们可以看到,当H2数据库无法访问时,Hystrix会自动回退到Redis来读取每本书的评级。可以在此处找到演示此用例的源代码。
6. 使用作用域
通常,@HystrixCommand注解方法在线程池上下文中执行。但有时它需要在本地作用域内运行,例如@SessionScope或@RequestScope。这可以通过为@HystrixCommand注解提供参数来完成:
@HystrixCommand(fallbackMethod = "getSomeDefault", commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")
})
7. Hystrix仪表板
Hystrix的一个不错的可选功能是能够在仪表板上监视其状态。
为了启用它,我们将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>
前者需要通过使用@EnableHystrixDashboard注解@Configuration来启用,后者会自动在我们的Web应用程序中启用所需的指标。
重新启动应用程序后,我们将浏览器指向http://localhost:8080/hystrix,输入Hystrix流的指标URL并开始监控。
最后,我们应该看到这样的东西:
监视Hystrix流是一件好事,但是如果我们必须监视多个启用Hystrix的应用程序,它就会变得不方便。为此,Spring Cloud提供了一个名为Turbine的工具,该工具可以聚合流以呈现在一个Hystrix仪表板中。
配置Turbine超出了本文的范围,但这里应该提到这种可能性。因此,也可以使用Turbine流通过消息传递来收集这些流。
8. 总结
正如我们目前所见,我们现在能够使用Spring Netflix Hystrix以及Spring RestTemplate或Spring Netflix Feign来实现断路器模式。
这意味着我们能够使用默认数据使用包含回退的服务,并且我们能够监控此数据的使用情况。
与往常一样,本教程的完整源代码可在GitHub上获得。