如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,知道提供者响应或超时,在高负载场景下,如果不做任何处理,那么就会导致服务消费者的资源耗尽甚至整个系统崩溃。微服务架构的应用系统通常包含多个服务层,微服务之间通过网络进行通信,从而支撑起整个应用系统,服务之间难免存在依赖关系,事实上,微服务并非总是能够保持可用状态,而网络也往往非常脆弱,因此难免有些请求会失败。
我们把“基础服务故障”而导致“级联故障”的现象称为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐传递放大的过程。
要想防止雪崩效应,必须有一个强大的容错机制,它应该实现以下两点:
- 为网络请求设置超时:通常情况下,一次远程调用对应着一个线程/进程,如果响应太慢,这个线程/进程就得不到释放,进而导致系统资源耗尽,最终导致服务不可用 。
- 使用断路器模式:就比如家里如果没有断路器,当电流过载时(功率过大、短路等),电路没有断开,电路就会迅速发热,烧断电路甚至引起火灾,使用断路器之后,一旦电流过载就会跳闸切断电路,从而保护电路安全。同理,如果某个微服务的请求大量超时了,再去产生新的请求已经没有任何意义,只会白白消耗资源。微服务里断路器可以理解为对容易导致错误的操作的一个代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。断路器可以快速失败,如果它在一段时间内检测到许多类似的错误,就会在之后的一段时间内,强迫对该服务的调用失败,不再请求所依赖的服务,这样就减少了cpu的超时等待时间。断路器也可以自动诊断依赖的服务是否已经恢复正常,这样,微服务就有了“自我修复”的功能:当依赖的服务不正常时开启快速失败机制,防止雪崩效应;当发现服务恢复正常时,又会恢复请求。
Hystrix就是spring cloud中一个断路器,它是由Netflix开源的一个延迟和容错库,用于隔离远程系统,服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错:
- 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行(这里用了“命令模式”)
- 跳闸机制:当某个服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止该服务的请求一段时间
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就会被立即拒绝,而不是排队等候,加速失败判定
- 监控:Hystrix可以近乎实时的监控运行指标和配置的变化,例如成功或失败等
- 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。这个可以由开发人员自行提供
- 自我修复:断路器打开一段时间后,会自动进入“半开”状态(探测服务是否可用,如还是不可用,再次退回打开状态)。
在Ribbon中使用Hystrix:
添加maven依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
在程序的启动类ServiceRibbonApplication 加@EnableHystrix注解开启Hystrix:
@SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient @EnableHystrix public class ServiceRibbonApplication { public static void main(String[] args) { SpringApplication.run( ServiceRibbonApplication.class, args ); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }
在service方法中添加@HystrixCommand注解,开启熔断功能,并指定回退方法fallbackMethod ,进行快速失败操作:
@Service public class HelloService { @Autowired RestTemplate restTemplate; @HystrixCommand(fallbackMethod = "hiError") public String hiService(String name) { return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class); } public String hiError(String name) { return "hi,"+name+",sorry,error!"; } }
在Feign中使用熔断器
在配置文件中打开:feign.hystrix.enabled=true
在FeignClient注解的接口方法上,增加了fallback指定类:
@FeignClient(value = "service-hi",fallback = SchedualServiceHiHystric.class) public interface SchedualServiceHi { @RequestMapping(value = "/hi",method = RequestMethod.GET) String sayHiFromClientOne(@RequestParam(value = "name") String name); }
然后实现该类和方法即可。这里的fallback方法执行并不代表断路器已经打开,请求失败、超时、被拒绝等情况下都会执行回退逻辑。
Hystrix线程隔离与传播上下文:
线程隔离的策略分两种:分别是线程隔离和信号量隔离。线程隔离表示HystrixCommand将会在单独的线程上执行,并发请求受线程池中线程数量的限制;信号量隔离是指HystrixCommand将会在调用线程上执行,开销相对较小,并发请求受信号量个数限制。Hystrix默认并推荐使用线程隔离模式,因为这种方式有除了网络超时以外的额外保护层,只有在调用负载非常高时才需要使用信号量,可以减少线程开销。