Spring Cloud 断路器 Hystrix
在微服务架构中,通常存在多个服务调用层。微服务之间通过网络进行通信,从而支撑起整个应用,为了保证高可用,单个服务通常也会集群部署。但由于网络原因或者自身原因,服务并不能保证100% 可用。而服务间的依赖关系,会导致故障传播,即服务提供者的不可用会导致消费者不可用,并把不可用逐渐放大,这就是雪崩效应。
这里就引入了请求超时机制和断路器模式
Hystrix 简介
Hystrix 是 Netflix 的开源组件,是一个用于实现超时机制和断路器模式的工具类库。在微服务架构中,通常存在多个服务调用层。较低级别的服务中的服务故障可能导致级联故障,一直到达用户。当对特定服务的调用超过一个阈值(默认是 10秒/20个请求/故障百分比 > 50%),断路器将打开并且不会进行调用。在出现错误和开路的情况下,开发人员可以提供一个回退
断路打开后可以防止级联故障。回退可以是另一个受 Hystrix 保护的调用、静态数据或合理的空值。
Ribbon 中整合 Hystrix
我们在 Spring Cloud 服务消费(Ribbon) 中的例子基础上添加 Hystrix
准备工作
- eureka-server 作为服务注册中心
- product-service 作为服务提供者
- 复制工程 order-service-ribbon 为 order-service-ribbon-hystrix 来整合 Hystrix
代码参考:https://github.com/morgan412/spring-cloud-demo/tree/master/netflix
添加 Hystrix 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主类上添加注解 @EnableCircuitBreaker
,开启断路器功能
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class OrderServiceRibbonHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceRibbonHystrixApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在需要容错的方法上添加注解 @HystrixCommand
,并指定回调(回退)方法
@Service
public class ProductService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getProductFallBack")
public Product getProduct(Long id) {
return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
}
public Product getProductFallBack(Long id) {
Product product = new Product();
product.setId(-1L);
product.setName("默认商品");
return product;
}
}
@HystrixCommand
的配置可以比较灵活,可以使用commandProperties
属性列出@HystrixProperty
注释,例如@HystrixCommand(fallbackMethod = "getProductFallBack", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"), @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000")}, threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "1"), @HystrixProperty(name = "maxQueueSize", value = "10") })
@RestController
@Log4j2
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.getProduct(id);
}
}
验证结果
- 启动项目 eureka-server、product-service、order-service-ribbon-hystrix
- 访问 http://localhost:8081/product/1 ,得到正常结果
- 关闭 product-service 服务,再访问 http://localhost:8081/product/1 ,得到结果
{"id":-1,"name":"默认商品","description":null,"price":null,"count":null}
。说明当调用的服务不可用时,执行了回退方法
Feign 使用 Hystrix
Feign 使用声明式的接口,没有方法体,所以通过注解的方式显然不适合 Feign。Spring Cloud 已经默认为 Feign 整合了 Hystrix。我们在 Spring Cloud 服务消费(Feign) 中的例子基础上使用 Hystrix
准备工作
- eureka-server 作为服务注册中心
- product-service 作为服务提供者
- 复制工程 order-service-feign 为 order-service-feign-hystrix 来使用 Hystrix
为 Feign 添加回退
Spring Cloud 已经默认为 Feign 整合了 Hystrix,所以我们不需要添加依赖。配置文件中需要把 feign.hystrix.enabled
属性设置为 true
feign:
hystrix:
enabled: true
改写 Feign 客户端接口,在 @FeignClient
注解中通过 fallback 属性指定回调类
@FeignClient(value = "product-service", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
@GetMapping("/product/{id}")
Product getProduct(@PathVariable Long id);
}
创建回调类,这个类需要实现 @FeignClient
的接口,其中实现的方法就是对应接口的回退方法
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProduct(Long id) {
Product product = new Product();
product.setId(-1L);
product.setName("默认商品");
return product;
}
}
@RestController
@Log4j2
public class ProductController {
@Autowired
private ProductFeignClient productFeignClient;
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable Long id) {
return productFeignClient.getProduct(id);
}
}
验证结果
- 启动项目 eureka-server、product-service、order-service-feign-hystrix
- 访问 http://localhost:8081/product/1 ,得到正常结果
- 关闭 product-service 服务,再访问 http://localhost:8081/product/1 ,得到结果
{"id":-1,"name":"默认商品","description":null,"price":null,"count":null}
。说明当调用的服务不可用时,执行了回退方法
通过 fallbackFactory 检查回退原因
如果需要查看回退原因,可以使用注解 @FeignClient
中的 fallbackFactory 属性
@FeignClient(value = "product-service", fallbackFactory = ProductClientFallbackFactoty.class)
public interface ProductFeignClient {
@GetMapping("/product/{id}")
Product getProduct(@PathVariable Long id);
}
同时创建 ProductClientFallbackFactoty 类,实现 FallbackFactory 接口,create 方法有个 Throwable 参数记录回退原因
@Component
@Log4j2
public class ProductClientFallbackFactoty implements FallbackFactory<ProductFeignClient> {
@Override
public ProductFeignClient create(Throwable throwable) {
return new ProductFeignClient() {
@Override
public Product getProduct(Long id) {
// 日志最好放在各个 fallback 方法中,而不要直接放在 create 方法中
// 否则在引用启动时会打印该日志
log.info("fallback, caused by:", throwable);
Product product = new Product();
product.setId(-1L);
product.setName("默认商品");
return product;
}
};
}
}
测试一下结果,当调用的服务不可用时,会进入回调方法中,并打印如下日志
2019-05-06 14:21:20.384 INFO 8056 --- [ HystrixTimer-1] c.t.o.c.ProductClientFallbackFactoty : fallback, caused by:
com.netflix.hystrix.exception.HystrixTimeoutException: null