雪崩概述:
1)当用户访问A服务的一个url接口时,部署A服务的tomcat会给用户分配一个线程,支持用户访问。
2)A服务要完成用户的操作,又需要访问B服务。
3)A服务又去访问B服务,部署B服务的tomcat会给A服务分配一个线程,支持A服务的访问。
4)B服务要完成A服务的操作,又需要访问C服务。
5)B服务又去访问C服务,但是C服务挂了;B服务在访问C服务之前不知道C服务挂了,B去访问,直到超时,才知道C服务无法访问。
结果:
因为C服务不可用,导致B服务的线程不能及时回收,从而导致A服务的线程也不能及时回收,最终导致整个服务链的线程池中没有线程可用了;此时如果再有用户访问A服务,那么tomcat会直接报503(服务不可用)。— 这就是服务雪崩,也叫服务的联动故障,服务的故障传播。
解决思路:
服务雪崩的本质其实是线程没有及时回收;不管是调用成功还是失败,只要线程能够及时回收,就可以解决服务雪崩问题。
方案一
将服务间的调用超时时长改小,这样就可以让线程及时回收 — 等待时间太久了不等了。
优点:非常简单,也可以有效的解决服务雪崩。
缺点:不够灵活,有的服务就是需要等待较长的时间,由于调用超时时长小,还没有访问到服务,线程就会被回收了。
方案二
设置拦截器:B服务调用C服务时会先经过拦截器,拦截器知道C服务的状态,在拦截器中会对C服务的状态进行判断,如果C服务正常则继续调用,如果C服务挂了则直接return。Hystrix的实现方案就是使用拦截器。
与OpenFign一起使用:
第一步:pom文件
Hystrix常和OpenFeign、Ribbon一起使用。 但是OpenFeign集成了Hystrix和Ribbon,所以只需要导入OpenFeign的依赖即可 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
hystrix的单独依赖 <!--引入的hystrix的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
第二步:创建在OpenFeign中声明的接口的实现类,并注入IOC容器
@Component public class UserOrderHystrix implements UserOrderFeign { //重写方法 }
第三步:修改OpenFegin中创建的接口,添加属性fallback,值为实现类的class实例
/* 1.接口名称为UserOrderFeign,表示是user-service服务访问order-service服务 的feign接口; 2.@FeignClient(value = "order-service")指定提供者服务(被访问的服务)的 应用(服务)名称; fallback = UserOrderHystrix.class指定备选方案为实现类UserOrderHystrix; */ @FeignClient(value = "order-service", fallback = UserOrderHystrix.class) public interface UserOrderFeign { /* 3.指定提供者服务处理请求的方法签名: 指定提供者服务处理请求的方法的名称、参数、返回值类型、标注的注解、 处理的请求的url -- 除了没有方法体以外,其它的和提供者服务处理请求 的方法一模一样; */ @RequestMapping("/doOrder") public String doOrder();
第四步:在controller中注入OpenFegin创建的接口
/* 注入UserOrderFeign: 会爆红,因为容器中目前有两个UserOrderFeign的bean对象,一个是OpenFeign 为其提供的代理对象,一个是我们定义的UserOrderFeign接口的实现类UserOrderHystrix 的bean对象 --- 不用管 */ @Autowired private UserOrderFeign userOrderFeign; //处理/userDoOrder的请求,并向客户端响应字符串文本 @RequestMapping("/userDoOrder") public String userDoOrder(){ /* 调用UserOrderFeign的doOrder()方法,即向order-service服务发出了 /doOrder的请求,并接收了其响应的字符串文本; 如果order-service服务正常,则执行order-service服务中处理/doOrder 请求的doOrder()方法;如果order-service服务出现问题,则执行我们定义 的UserOrderFeign接口的实现类UserOrderHystrix中重写的doOrder()方法; */ String result = userOrderFeign.doOrder(); //将接收的order-service服务的/doOrder请求响应的字符串文本再响应给客户端 return result; }
第五步:修改application.properties
#开启hystrix断路器 feign.hystrix.enabled=true
注意事项:
#因为是与OpenFeign一起使用的,所以主启动类上一定不能忘记添加注解 @EnableFeignClients
与Ribbon一起使用:
第一步:pom文件
Hystrix常和OpenFeign、Ribbon一起使用。 但是OpenFeign集成了Hystrix和Ribbon,所以只需要导入OpenFeign的依赖即可 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
hystrix的单独依赖 <!--引入的hystrix的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
第二步:主启动类
//开启Hystrix断路器:或者使用@EnableCircuitBreaker,因为@EnableHystrix中包含的就是@EnableCircuitBreaker @EnableHystrix //标注@EnableFeignClients注解,表示此应用(服务)是被feign管理的客户端服务 @EnableFeignClients //标记此应用(服务)为eureka客户端 @EnableEurekaClient @SpringBootApplication public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }
第三步:配置类
//配置RestTemplate的bean对象到容器,使用其可以发送请求 @Bean //标注@LoadBalanced注解,表示让ribbon来管理RestTemplate @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
第四步:controller中
//注入RestTemplate @Autowired private RestTemplate restTemplate;
//声明备选方案 public String userDoOrder2RibbonHytrix(String serverName){ return "下单失败喽"; } //备选方案方法声明要求:修饰符,返回值,属性名,属性个数必须与方法保持一致,不用加注解
//本来的url访问的处理方法上添加注解@HystrixCommand //给出属性值(fallbackMethod = "备选方案的方法名") /* 指定备选方案为userDoOrder2RibbonHytrix()方法: 如果order-service服务正常,则执行order-service服务中处理/doOrder请求的 doOrder()方法;如果order-service服务出现问题,则执行userDoOrder2RibbonHytrix() 方法; */ @HystrixCommand(fallbackMethod = "userDoOrder2RibbonHytrix") @RequestMapping("/userDoOrder2") public String userDoOrder2(String serverName){ //组装url String url = "http://" + serverName + "/doOrder"; //发出请求,并接收order-service服务的/doOrder请求响应的字符串文本 String result = restTemplate.getForObject(url, String.class); //将接收的order-service服务的/doOrder请求响应的字符串文本再响应给客户端 return result; }
遇到的一个bug,导入依赖后,无法使用@HystrixCommand注解 解决方法:import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 在最上边手动导入,导入后会报错,idea使用alt+enter后会自动去修改pom文件,再idea自动修改后,这个注解就可以使用了
遇到的小问题:在使用ribbon实现服务调用的时候,如果被调用方是一个集群,根据负载均衡,会将url依次分发,但是将其中某一台服务挂掉以后,负载均衡发送的请求依旧发送到了挂了的服务器,没有将其发送到正常的服务器。 原因:因为调用方会周期性的拉去注册中心中的服务列表,所以在被调用的服务挂掉后,而还没到时间去注册中心再次拉取服务列表,导致这样一个小bug
注意事项:
#主启动类上注解: #开启Hystrix断路器:或者使用@EnableCircuitBreaker,因为@EnableHystrix中包含的就是@EnableCircuitBreaker @EnableHystrix #标注@EnableFeignClients注解,表示此应用(服务)是被feign管理的客户端服务 @EnableFeignClients #不能忘!!!!
相关概念:
降级:是指当请求超时、资源不足等情况发生时,进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。
//OpenFegin版 @FeignClient(value = "服务名", fallback = 实现类.class) //Ribbon版: @HystrixCommand(fallbackMethod = "controller中声明的备选方法名")
熔断:当一定时间内,异常请求(请求超时、网络故障、服务异常等)比例达到阈值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快速返回托底数据(降级),保证服务链的完整,避免服务雪崩。
熔断器有自动恢复机制。如:当熔断器启动后,每隔5秒,尝试将新的请求发送给服务,如果服务可正常执行并返回结果,则关闭熔断器,服务恢复;如果仍旧调用失败,则继续返回托底数据,熔断器持续开启状态。
降级仅是出错了返回托底数据;而熔断是出错后开启熔断器返回托底数据,且在一定时间内不再访问服务。
资源隔离策略:Hystrix的资源隔离策略有两种:线程池和信号量。
**使用线程池:**当A服务的某个任务线程出现阻塞的时候,该服务的任务线程会被设置为不可用,但该A服务的其他任务线程依旧可以使用,避免了一旦某个任务出现阻塞导致整个A服务直接崩掉。
**使用信号量:**采用信号量隔离策略,每接收一个请求,都是服务自身线程去直接调用被访问服务,信号量就相当于一个关卡,每个线程通过关卡后,信号量数量减1,当为0时不再允许线程通过,而是直接执行fallback返回托底数据,仅仅做了一个限流;
常用的其他配置:
其他配置:
hystrix.command.default.execution.isolation.Strategy #配置资源隔离策略,THREAD线程池(默认值),SEMAPHORE信号量。 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds #设置请求超时时长,默认1000毫秒。 hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests #设置最大并发请求数(即线程数),默认10。 hystrix.command.default.circuitBreaker.enabled #设置是否启用熔断器机制,默认true。 hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds #设置当熔断器启动后,再次尝试请求服务的窗口时间(间隔时间),默认5000毫秒。 hystrix.command.default.circuitBreaker.requestVolumeThreshold #设置请求失败启用熔断器的阈值,即在窗口时间内请求失败多少次启用熔断器,默认20。 hystrix.command.default.circuitBreaker.errorThresholdPercentage #设置请求失败启用熔断器的比例阈值,即在窗口时间内请求失败次数达到此比例值则启动熔断器,默认50。
说明:
default是全局配置,即对提供者服务所有url接口的方法的调用的配置;如果仅对提供者服务指定url接口的方法的调用进行配置,则将default改为提供者服务对应url接口的方法名。