SpringCloud Netflix (五) : Hystrix 服务熔断和服务降级
什么是Hystrix
在分布式环境中,许多服务依赖项中的一些服务依赖不可避免地会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、防止服务之间的级联故障以及提供回退选项来实现这一点,所有这些都提高了系统的总体弹性。
( 级联故障 )
Hystrix可以做什么
-
延迟和容错
防止级联故障。回退和优雅的降级。快速恢复失败。
带断路器的线程和信号量隔离。
-
实时操作
实时监控和配置更改。当服务和属性变更在集群中传播时,立即生效。
保持警觉,做出决定,影响变化,并在几秒钟内看到结果。
-
并发性
并行执行。支持并发的请求缓存。通过请求折叠自动批处理。
服务熔断和服务降级
服务雪崩
服务熔断和服务降级是解决服务雪崩的手段之一,所以在了解服务熔断和服务降级前,需要先明白什么是服务雪崩。如下图所示,因评论服务的失败,导致整个服务链条的失败,即一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩。
服务熔断
如上图所示,如果当评论服务不可用或响应过慢时,常理来说,应该等到评论服务恢复可用再来调用,可事实上是,后续每个评论服务请求,还是会等待评论服务响应,这可能会消耗商品详情服务的宝贵资源,如线程,导致资源耗尽,从而使商品详情服务无法处理其他请求。而服务熔断就是解决这个问题的。
当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。需要说明的是熔断其实是一个框架级的处理,而基本上业内用的是断路器模式;
注意,这时商品详情服务还是会因为评论服务请求失败报Hystrix circuit short-circuited and is OPEN异常
而不可用,单纯的服务熔断只是避免重复调用不可用的评论服务而已,不要把熔断和熔断降级归为一起,后面实现可以看一些区别。
断路器背后的基本思想非常简单。在断路器对象中包装受保护的函数调用,该对象监视故障。一旦故障达到某一阈值,断路器就会跳闸,所有对断路器的进一步呼叫都会返回错误,而根本不进行受保护的呼叫。通常,如果断路器跳闸,您还需要某种监视器警报。 ---Martin Fowler
那断路器什么时候打开和关闭呢?
以Hystrix的断路器为例,每当20个请求中,有50%失败时,断路器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,会跳到半开模式,放一次请求进来,重新检测服务是否恢复正常,判断是否把断路器关闭,或者继续打开。
服务降级
服务熔断虽然避免了许多无用的调用,但是商品详情服务还是会因为相比不太重要的评论服务失败而不可用,那是不合理的。那能不能在评论服务请求失败时,不影响商品详情服务的正常使用呢?这时候就需要使用服务降级了。(注意,服务降级有很多种降级方式!如开关降级、限流降级、熔断降级! 熔断降级是采用了服务熔断的降级方式,可以说熔断降级是服务降级方式的一种,不要把熔断降级想为单单是熔断)
降级有两种场景:
-
当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度。(开关降级)
-
当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户。(熔断降级)
Hyrtix实现服务熔断和服务降级
创建hystrix-details、hystrix-comment、hystrix-goods、hystrix-price服务模拟上图4个服务,并4个服务注册到注册中心。
添加Hystrix依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.7.RELEASE<version> </dependency>
实现服务熔断
在hystrix-details的启动类上添加注解@EnableCircuitBreaker
,允许创建断路器
@SpringBootApplication @EnableEurekaClient @EnableFeignClients @EnableCircuitBreaker public class HystrixDetails { public static void main(String[] args) { SpringApplication.run(HystrixDetails.class, args); } }
@GetMapping("/details") @HystrixCommand public String details() throws RuntimeException, InterruptedException{ return "goods:"+goodsFeignClient.goods()+ " price:"+priceFeignClient.price()+ " comment:"+commentFeignClient.comment(); }
在hystrix-comment被调用的方法里添一行错误代码或线程睡眠,模拟服务不可用或响应过慢,启动各个服务,访问 hystrix-details里details()方法地址localhost:8080/details,快速刷新20次以上,触发断路器打开。
之后查看控制台,会发现当断路器打开后,hystrix-comment报错时间异常间隔为5秒 (hystrix休眠窗时间) ,hystrix-details报错异常从feign.FeignException: status 500 reading CommentFeignClient#comment(); content:
变成java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN
,说明在断路器打开的时间内,hystrix-details对hystrix-comment请求并没有进入到hystrix-comment服务中,而是被断路器拦截了,服务熔断实现成功。一般不会单独使用熔断,而会使用熔断+降级的熔断降级。
实现服务降级(熔断降级)
在之前代码中的注解@HystrixCommand
里添加 fallbackMethod = "detailsFallback"
@GetMapping("/details") @HystrixCommand(fallbackMethod = "detailsFallback") public String details() throws RuntimeException, InterruptedException{ return "goods:"+goodsFeignClient.goods()+ " price:"+priceFeignClient.price()+ " comment:"+commentFeignClient.comment(); } public String detailsFallback(){ return "goods:"+goodsFeignClient.goods()+ " price:"+priceFeignClient.price()+ " comment:error"; }
这时候访问localhost:8080/details,hystrix-detail服务请求hystrix-comment服务失败后会触发降级,调用退步方法fallback()
,hystrix-detail服务不会报异常,页面状态200正常。在这里的fallback
FallbackFactory
当我们实际使用服务降级时,不应该使用上面这种方式。当一个访问中要调用多个服务时,fallback的回退方法就会非常臃肿,后期维护困难,代码耦合度高,且一个方法一个fallback也增加了代码量。所以我们应该面向服务,把每个服务的fallback包装起来,在调用服务的接口上实现fallback,FallbackFactory就是hystrix提供给我们来实现这一举措的。
1.创建一个CommentFallbackFactory类实现FallbackFactory<T>
接口
@Component public class CommentFallbackFactory implements FallbackFactory<CommentFeignClient> { @Override public CommentFeignClient create(Throwable throwable) { return new CommentFeignClient() { @Override public String comment() { return "error"; } }; } }
2.在调用hystrix-comment服务的feign接口的@FeignClient
里加上
fallbackFactory = CommentFallbackFactory.class
@Component //必须填加,否则应用会扫描不到 @FeignClient(value = "HYSTRIX-COMMENT", fallbackFactory = CommentFallbackFactory.class) public interface CommentFeignClient { @GetMapping("/") String comment(); }
feign允许开启hystrix后, 会自动把所有服务的feign接口下的方法加入到断路器监控中
feign:
hystrix:
enabled: true
HystrixCommandProperties
我们可以在配置里修改 HystrixCommandProperties 类(在com.netflix.hystrix包下)里的变量,包括修改断路器的休眠窗时间circuitBreakerSleepWindowInMilliseconds
、修改响应超时时间executionTimeoutInMilliseconds
hystrix: command: default: #default全局有效,service id指定应用有效 #配置的属性名在HystrixCommandProperties类的构造方法下可以找到 execution: timeout: enabled: true #是否开启超时熔断 circuitBreaker: sleepWindowInMilliseconds: 10000 #把断路器的休眠窗时间设为10秒,默认为5秒
Hystrix的主要好处之一是它收集的有关每个HystrixCommand的一组度量。Hystrix仪表板以有效的方式显示每个断路器的运行状况。
1.新建module,springcloud-consumer-hystrix-dashboard
添加 hystrix-dashboard 相关依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId> <version>1.4.7.RELEASE</version> </dependency>
添加配置
@SpringBootApplication //开启仪表盘 @EnableHystrixDashboard public class HystrixDashboard { public static void main(String[] args) { SpringApplication.run(HystrixDashboard.class,args); } }
2.在服务提供者
添加监控依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
@SpringBootApplication @MapperScan("com.example.springcloud.mapper") @EnableDiscoveryClient public class Provider_8002 { public static void main(String[] args) { SpringApplication.run(Provider_8002.class,args); } @Bean public ServletRegistrationBean servletRegistrationBean(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet()); registrationBean.addUrlMappings("/actuator/hystrix.stream"); return registrationBean; } }
3.测试访问
访问 localhost:9001/hystrix
输入要监控的微服务 http://localhost:8002/actuator/hystrix.stream