在一个具有多服务的应用中,假如由于其中某一个服务出现问题,导致响应速度变慢,或是根本没有响应返回,会导致它的服务消费者由于长时间的等待,消耗尽线程,进而影响到对其他服务的线程调用,进而会转变为整个应用的故障。这也被称之为雪崩效应。
而Hystrix熔断器,正是用来帮助我们解决这种问题的工具。
Hystrix提供了熔断、隔离、fallback、cache、监控等功能,能够在一个或多个服务出现问题的时候保证整个应用依然处于可用状态。
没有做熔断处理的应用,出现问题后:
正常情况: A→B→C→D
D服务出现了问题: A→B→C×D
从而导致C服务无法获取正确的响应,出现对应的问题: A→B×C×D
最后导致B服务,甚至A服务都同样出现问题,由服务不可用变为应用不可用: A×B×C×D
针对于上面的这种情况,在Hystrix中采用了如下的几种方式进行处理。
1.Hystrix请求超时
用hystrix监控请求的响应时间,如果超过我们设置的时间,将会被判定为请求超时,抛出TimeoutException。
(1)引入Maven依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.2.7.RELEASE</version> </dependency>
(2)在properties中进行配置。
关于参数详解可以参考https://blog.csdn.net/u013889359/article/details/80118884、https://blog.csdn.net/tongtong_use/article/details/78611225、https://blog.csdn.net/nb7474/article/details/84440822
#超时时间,默认1000,单位ms。
#注意这里配置的default,默认所有的请求服务都被设定为这个值。如果要针对某一个服务,可以将default改为服务名如user,或在请求类中通过注解进行单独配置 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000
(3)在启动类中添加注解@EnableCircuitBreaker
@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class RoleServerApplication { public static void main(String[] args) { SpringApplication.run(RoleServerApplication.class, args); } }
(4)在需要被Hystrix控制请求时间的方法上添加注解@HystrixCommand。如果方法没有被配置这个注解,请求将不会达到指定时间后抛出超时异常。
@GetMapping("roles/{id}") @HystrixCommand public String getRole(@PathVariable("id") String id) { System.out.println("接收到请求[/roles/" + id + "]"); return restTemplate.getForObject(rest_url_prefix + "/users/" + id, String.class); }
如此完成了一个请求的超时配置,假如方法getRole()对/users/{id}请求的时间超过了一秒没有得到相应,那么会抛出如下异常:
2. 对服务分别配置线程池调用
不同的服务配置了不同的线程池,这样可以即使向服务A发送的请求都超时,占满了线程数。但是向服务B发送的请求仍然处于正常状态,不会受到A服务的干扰。
(1)引入MAVEN依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.2.7.RELEASE</version> </dependency>
(2)在properties中进行配置(注意这里配置的都是default,默认适应所有的被@HystrixCommand修饰的方法中的请求,如果需要分别针对不同的服务,替换defalut即可)。
#超时时间,默认1000,单位ms。 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=100000 #线程池核心线程数,最多同时只有2个线程在执行 hystrix.threadpool.default.coreSize=2 #线程池最大队列数,也可以理解为最多只能添加4个请求进来。包括正在执行的请求。 hystrix.threadpool.default.maxQueueSize=4 #线程池最大线程数。最多只能接收2+3=5个线程数。 hystrix.threadpool.default.maximumSize=3 #队列拒绝阈值,即使队列数没有达到maxQueueSize,也会拒绝接收任务 hystrix.threadpool.default.queueSizeRejectionThreshold=6
(3)在启动类中添加注解@EnableCircuitBreaker
@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class RoleServerApplication { public static void main(String[] args) { SpringApplication.run(RoleServerApplication.class, args); } }
(4)在需要被Hystrix控制请求时间的方法上添加注解@HystrixCommand,同时在注解内填入应急方法的方法名,并定义好应急方法。
@GetMapping("roles/{id}") @HystrixCommand(fallbackMethod = "getRoleFallbackMethod") public String getRole(@PathVariable("id") String id) { System.out.println(LocalDateTime.now().toString() + "接收到请求[/roles/" + id + "]"); return restTemplate.getForObject(rest_url_prefix + "/users/" + id, String.class); } public String getRoleFallbackMethod(String id) { System.out.println(LocalDateTime.now().toString() + "进入fallBackMethod!"); return "进入fallBackMethod"; }
上面的步骤执行完成后,我们再多次调用getRole(String)方法,根据我们上面配置的参数,设置超时时间100秒是为了让请求一直保持存活状态。
假如请求的线程数超过了线程池中允许的最大线程,会抛出线程池拒绝异常,或是进入fallback方法。
3.Hystrix服务熔断
不论是请求超时还是线程池的配置,服务熔断做的更为彻底。
在一定的时间范围内,向A服务发送的请求失败率达到了一个指定的比例,会开启熔断器。熔断器开启后,所有向A服务发起的请求都将不会被发起请求,而是或抛出异常,或调用fallback方法。当熔断器开启达到指定时间后,会重新向A服务发起一次请求,如果请求失败,熔断器继续保持开启状态。如果请求成功,则熔断器关闭。
(1)引入MAVEN依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.2.7.RELEASE</version> </dependency>
(2)在properties中进行配置(注意这里配置的都是default,默认适应所有的被@HystrixCommand修饰的方法中的请求)。
#一个rolling window内最小的请求数。如果请求数少于该值,则不论如何也不会触发短路。 hystrix.command.default.circuitBreaker.requestVolumeThreshold=3 #错误率阈值。如果一个rolling window内的请求错误率超过了该值,则会开启熔断器 hystrix.command.default.circuitBreaker.errorThresholdPercentage=0.5 #触发短路的时间值,单位毫秒。 hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
(3)在启动类中添加注解@EnableCircuitBreaker
@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class RoleServerApplication { public static void main(String[] args) { SpringApplication.run(RoleServerApplication.class, args); } }
(4)在需要被Hystrix控制请求时间的方法上添加注解@HystrixCommand,同时在注解内填入应急方法的方法名,并定义好应急方法。
@GetMapping("roles/{id}") @HystrixCommand(fallbackMethod = "getRoleFallbackMethod") public String getRole(@PathVariable("id") String id) { System.out.println(LocalDateTime.now().toString() + "接收到请求[/roles/" + id + "]"); return restTemplate.getForObject(rest_url_prefix + "/users/" + id, String.class); } public String getRoleFallbackMethod(String id) { System.out.println(LocalDateTime.now().toString() + "进入fallBackMethod!"); return "进入fallBackMethod"; }
(5)这里我们编写一个服务提供者代码,假如接收到的id中有1,那么会正确返回,如果接收到的id中没有1,那么会休眠100秒,导致请求超时。
@GetMapping("users/{id}") public String getUser(@PathVariable("id") String id) { if (StringUtils.contains(id, "1")) { return "ok"; } try { Thread.sleep(100*1000); } catch (InterruptedException e) { e.printStackTrace(); } String user = service.findUser(id); return user; }
请求结果:
我们可以先测试id=1的请求,如下图,发现请求正常,此时circuit处于closed状态。
然后我们再发送几条id为其他值的请求,此时发现请求error,circuit已经变成了open状态。这个时候即使我们发送id=1的请求也同样会被进入到fallback方法中。
只有当circuit被开启了指定的sleepWindowInMillisecond数后,再发送id=1的数据,circuit会重新变为close。