🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 简介 在client端配置 ~~~ <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> ~~~ Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败 判定时间。 用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时, 则会进行降级处理,什么是服务降级? > 服务降级:优先保证核心服务,而非核心服务不可用或弱可用。 用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回 友好的提示信息)。 服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它 服务没有响应。 触发Hystix服务降级的情况: * 线程池已满 * 请求超时 # 模式 **资源隔离**        货船为了进行防止漏水和火灾的扩散,会将货仓分隔为多个,这种资源隔离减少风险的方式被称为:Bulkheads(舱壁隔离模式)。        Hystrix将同样的模式运用到了服务调用者上,在一个高度服务化的系统中,一个业务逻辑通常会依赖多个服务,比如:商品详情展示服务会依赖商品服务,价格服务,商品评论服务。调用三个依赖服务会共享商品详情服务的线程池。如果其中的商品评论服务不可用,就会出现线程池里所有线程都因等待响应而被阻塞,从而造成服务雪崩。Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离,从而避免服务雪崩。 **熔断器模式** 熔断器模式定义了熔断器开关相互转换的逻辑。        服务的健康状况 = 请求失败数 / 请求总数。熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的。        当熔断器开关关闭时,请求被允许通过熔断器。 如果当前健康状况高于设定阈值,开关继续保持关闭。如果当前健康状况低于设定阈值,开关则切换为打开状态。当熔断器开关打开时,请求被禁止通过。当熔断器开关处于打开状态,经过一段时间后,熔断器会自动进入半开状态,这时熔断器只允许一个请求通过。当该请求调用成功时,熔断器恢复到关闭状态。若该请求失败,熔断器继续保持打开状态,接下来的请求被禁止通过。        熔断器的开关能保证服务调用者在调用异常服务时,快速返回结果,避免大量的同步等待,并且熔断器能在一段时间后继续侦测请求执行结果,提供恢复服务调用的可能。 ![](https://img.kancloud.cn/5d/77/5d773058c1a5e21c6b4df15f5f3e1f02_1011x725.png) 状态机有三种状态 * closed: 关闭状态(断路器关闭),所有请求都正常访问 * open: 打开状态(断路器打开),所有请求都会被降级.会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭.默认失败比例的阈值是50%.请求次数最少不低于20次. * Half open: 半开状态,closed状态不是永久的,关闭后会进入休眠时间(默认是5s).随后断路器会自动进入半开状态,此时会释放部分请求通过,若这些请求都是健康的,则会完全打开断路器,否则继续保持关闭,再次进行休眠计时 **命令模式** Hystrix使用命令模式(继承HystrixCommand类)来包裹具体的服务调用逻辑(run方法),并在命令模式中添加了服务调用失败后的降级逻辑(getFallback)。 同时在Command的构造方法中可以定义当前服务线程池和熔断器的相关参数。因此在使用了Command模式构建了服务对象之后,服务便拥有了熔断器和线程池的功能。 # client配置 有了SpringCloudApplication注解就不用配置其他了 ~~~ //@EnableCircuitBreaker @SpringCloudApplication public class ConsumerApplication { ~~~ # Fallback client调用放,HystrixCommand注解,里面配置失败时调用的方法. 要求queryByIdFallback和这个原来方法的返回值要一样 ~~~ @GetMapping("/{id}") @HystrixCommand(fallbackMethod = "queryByIdFallback") //失败时调用里面的 public String queryById(@PathVariable("id") Long id) { //------------------ } ~~~ ~~~ public String queryByIdFallback(Long id) { return "不好意思,服务器太拥挤"; } ~~~ 上面配置是单个的,如果你想配置局部的,那要写在类上 ~~~ @RestController @DefaultProperties(defaultFallback = "defaultFallback") @RequestMapping("consumer") public class ConsumerController { @GetMapping("/{id}") @HystrixCommand //不写走默认 public String queryById(@PathVariable("id") Long id) { //--- } } ~~~ ~~~ //必须是空参的,因为你写参数,那和其他的不一定对应上 public String defaultFallback() { return "不好意思,服务器太拥挤"; } ~~~ ## 设置超时时间 局部配置 ~~~ //2s超时 @GetMapping("/{id}") @HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") }) public String queryById(@PathVariable("id") Long id) { ~~~ 全局配置,配置文件这个可以不用在局部加注解了 可以指定全局,也可以指定某个服务的名称. 当然你也可以把user-serivce改为某个方法的名称 ~~~ hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 2000 user-serivce: execution: isolation: thread: timeoutInMilliseconds: 2000 ~~~ # 熔断 ~~~ @GetMapping("/{id}") @HystrixCommand(commandProperties = { //请求量,达到多少次统计一下,触发熔断的最小请求次数,默认20 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //休眠时间窗,毫秒,默认5000 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "100000"), //错误百分比,默认50% @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), }) public String queryById(@PathVariable("id") Long id) { if (id % 2 == 0) { throw new RuntimeException("---"); } ~~~ # 异常处理 ## 异常类型 Hystrix 的异常处理中,有5种出错的情况下会被 fallback 所截获,从而触发 fallback,这些情况是: * FAILURE:执行失败,抛出异常。 * TIMEOUT:执行超时。 * SHORT_CIRCUITED:断路器打开。 * THREAD_POOL_REJECTED:线程池拒绝。 * SEMAPHORE_REJECTED:信号量拒绝。 有一种类型的异常是不会触发 fallback 且不会被计数进入熔断的,它是 BAD_REQUEST,会抛出 HystrixBadRequestException,这种异常一般对应的是由非法参数或者一些非系统异常引起的,对于这类异常可以根据响应创建对应的异常进行异常封装或者直接处理。 如果使用@HystrixCommand注解,则只需要在降级函数中增加Throwable  e对象的定义 ~~~ @HystrixCommand(fallbackMethod="helloBackMethodFirst",ignoreExceptions=HystrixBadRequestException.class) ~~~ ## 获取触发异常 **注解方式** ~~~ @HystrixCommand(fallbackMethod = "fallback") User getUserById(String id) { throw new RuntimeException("getUserById command failed"); } User fallback(String id, Throwable throwable) { return new User("def", "def"); } ~~~ 这里定义了一个主逻辑函数`getUserById`,主逻辑中会主动抛出一个异常,从而触发该主逻辑的降级函数`fallback`。重点看`fallback`函数中的最后一个传参`Throwable throwable`。通过这样的简单定义,开发人员就可以很方便的获取触发降级逻辑的异常信息,用作日志记录或者其它复杂的业务逻辑了。 **继承方式** 在使用继承方式的时候通过`getFailedExecutionException`方法就可以获取到触发降级的异常信息了。 ~~~ public static class UserCommand extends HystrixCommand<User> { protected UserCommand() { s uper(HystrixCommandGroupKey.Factory.asKey("UserCommand")); } @Override protected User run() throws Exception { throw new RuntimeException("getUserById command failed"); } @Override protected User getFallback() { System.out.println(getFailedExecutionException().getMessage()); return new User("def", "def"); } } ~~~