Spring Cloud :4 . Hystrix 降级、熔断、限流
简介
在项目上线后,微服务之间相互调用,不可能一点问题都不出。如果出了问题,请求超时,应该怎么去解决?
首先请求超时可能会发生在两个阶段:
- 在客户端和服务器端建立连接的时候。
- 服务器端在处理业务逻辑的时候。
超时的时候,客户端不可能一直无限等待,这个时候如果有其他Client调用该服务,由于当前provider处理过慢,导致请求的淤积,久而久之这个服务的资源占用就会达到瓶颈而Down掉,微服务和微服务之间调用,其中一个微服务Down可能会导致整个服务链不可用,就会引起严重的服务雪崩。
而 Hystrix 就是解决微服务 与 微服务 之间互相调用的时候出现问题的解决方案,包含降级,熔断,和限流(隔离)!
- 降级:因为网络抖动,发生异常等原因,对连接或业务逻辑处理的超时,返回一个 可以接受 的结果!(服务调用方发起的自我保护机制,为了保证主业务不受影响,跳过调用持续报错,返回一个可以接受的结果,可以把这动态开关配置在配置中心)
降级的另外一种解释:在高并发期间主动下线那些非核心的流程,给主业务让路。
- 熔断:在一个时间窗内,多次请求一个服务的连续失败次数达到阈值,就会触发熔断,关闭对该服务的访问,直接调用降级方法。(服务提供方,发起的一个自我保护机制。触发机制可以是访问人数,错误次数,超时时间等)
- 限流:一个服务可能是有多个子服务组成,对每个子服的调用进行限流(限制线程数量 -> 线程池/信号量),避免了某个子服务占用了太多的资源而影响其他子服务的调用。
降级的目的不是快速 返回服务不可用,而是返回一个 兜底数据(凑合能用,服务可用)
自己写一个Hystrix的思路
/**
*
* try{
*
* 1.发起向服务方的请求
* 1.1 判断是否超时
* ——> 这次请求记录到服务里(一个时间片内,feign不会再请求这个服务)
* 1.2 尝试向其他服务器发送请求
* 其他服务器依然没有成功 -> 抛出异常(降级代码)
*
*
* 限流:(使用在provider端,对每个服务限流,一个服务的爆满 不会 影响其他服务,对服务进行隔离)
* 调用http请求会消耗线程,每个http请求调动服务都会开启一个线程,容易引起服务淤积导致服务雪崩。
* 可以使用两种方式: 1.使用map(URI,线程数量) 2.线程池(线程数)
* if(线程数满了){
* throw exception; 降级(及快速的失败)
* }
*
* 熔断: 对请求失败做计数( count++ )
* if(count>阈值){
* 熔断状态:开|关|半开 -> 半开:设置时间片,请求成功则重置count
* throw exception;降级(及快速的失败)
* }
*
* }catch(Exception e){
*
* 降级方案:
* 1.返回一个友好的页面。 -> 好看点的页面,重试按钮
* 2.返回另一个东西 -> 写到 mq 里
* return " 客户端稍后再来"
* }
*
*
*/
如果要把try…catch升级,可以使用aop来做,对RestTemplate进行代理包装,更方便的方式是包装成注解的方式。
降级解决了什么问题? 释放资源,不影响其他服务
Hystrix整合Feign搭建
Hystrix整合Feign有两种方式搭建:
在Consumer端 引入hystrix包:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置文件开启对Hystrix的支持:
#对 hystrix 的支持
feign.hystrix.enabled=true
fallback方式
在@FeignClient
注解上添加CallBack属性:
//FeignProviderCallBack类要交给SpringIOC管理,不然无法加载到
@FeignClient(name = "open-feign-provider",fallback = FeignProviderCallBack.class)
public interface UesrApiProxy extends UserAPI { //UserAPI 为公共接口,可看上一张Feign的搭建
}
创建 FeignProviderCallBack
类,并实现UesrApiProxy
接口:
@Component //交给SpringIOC
public class FeignProviderCallBack implements UesrApiProxy{ //这里踩坑了,实现的是标有@FeignClient的接口,而不是UserAPI
@Override
public String alive() {
return "降级了";
}
@Override
public String post(String name) {
return null;
}
}
触发降级,可能是连接超时,也有可能是服务处理超时异常.
如果想根据不同的异常对前端返回不同的信息,可以使用 fallbackFactory 的方式来实现。
fallbackFactory方式
修改@FeignClient
注解的属性:
//@FeignClient(name = "open-feign-provider",fallback = FeignProviderCallBack.class)
@FeignClient(name = "open-feign-provider",fallbackFactory = FeignProviderCallBackFactory.class)
public interface UesrApiProxy extends UserAPI {
}
创建FeignProviderCallBackFactory
类,并实现 FallbackFactory 接口
:
@Component //在实现FallbackFactory接口的同时要给出泛型,@FeignClient的标记类
public class FeignProviderCallBackFactory implements FallbackFactory<UesrApiProxy> {
@Override //这里会获取错误信息(包含本地和远端的异常),降级时,此方法会被回调
public UesrApiProxy create(Throwable throwable) {
return new UesrApiProxy() {
@Override
public String alive() {
System.out.println(throwable.getMessage());
return "使用FallbackFactory降级";
}
@Override
public String post(String name) {
return null;
}
};
}
}
Hystrix整合RestTemplate搭建
首先再启动类上添加注解 @EnableCircuitBreaker
,并且把RestTemplate
注入到 IOC 容器:
@SpringBootApplication
@EnableFeignClients //如果需要使用feign发起远程调用,需要添加这个注解
@EnableCircuitBreaker //Hystrix整合RestTemplate需要添加此注解才能正常使用
public class OpenFeignConsumer90Application {
public static void main(String[] args) {
SpringApplication.run(OpenFeignConsumer90Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
建立Service层里添加 @HystrixCommand
注解指定降级,虽然也可以把降级代码直接写到Controller层,但是不建议这样做:
@Service
public class RestService {
@Autowired
private RestTemplate restTemplate;
private final String URL_USER_PROVIDER="http://open-feign-provider/";
//指定降级方法
@HystrixCommand(fallbackMethod = "aliveFallBack")
public String alive(){
return restTemplate.getForObject(URL_USER_PROVIDER+"alive",String.class);
}
//降级方法
public String aliveFallBack(){
return "RestTemplate整合Hystrix降级了";
}
}
Controller层调用:
@Autowired //注入之前建立的Service
private RestService restService;
@GetMapping("/restHystrix")
public String restHystrix(){
return restService.alive();
}
provider端代码:
@Override
public String alive() {
//除0异常
int j= 5/0;
int i = count.getAndIncrement();
System.out.println("====坏的第:" + i + "次调用");
return "port:" + port;
}
访问结果:
线程池隔离与信号量隔离
Hystrix 对每个URI 进行限流 达到隔离的效果,一共有两种方式:
- 线程池隔离(官方推荐)
- 信号量隔离 Semaphore
线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。
信号量隔离主要维护的是Tomcat的线程,不需要内部线程池,更加轻量级。
线程池方式的优点:
- 失败策略更灵活。
- 可以做异步,让Tomcat的worker线程可以做更多的事情。
- 线程池内部的,异常的隔离
适合IO密集型,并发异步执行,可以提高效率
信号量什么时候使用?
- 代码健壮,几乎没有bug。
- 计算密集型,响应特别快
在以上两种情况中,如果使用线程池,反而需要增加维护线程池的成本
# thread 通过线程数量来限制并发请求数,可以提供额外的保护,但有一定的延迟。一般用于网络调用
# semaphore 通过semaphore count来限制并发请求数,适用于无网络的高并发请求
# 隔离策略,默认是Thread, 可选Thread|Semaphore
hystrix.command.default.execution.isolation.strategy
# 命令执行超时时间,默认1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
# 执行是否启用超时,默认启用true
hystrix.command.default.execution.timeout.enabled
# 发生超时是是否中断,默认true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout
# 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
#semaphore应该占整个容器(tomcat)的线程池的一小部分。
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests
dashboard监控
添加依赖:
<!--图形界面-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>
spring-cloud-starter-netflix-hystrix-dashboard
</artifactId>
</dependency>
<!--actuator监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置文件:
management.endpoints.web.exposure.include=*
在启动类上添加@EnableHystrixDashboard
注解:
@SpringBootApplication
@EnableFeignClients //如果需要使用feign发起远程调用,需要添加这个注解
@EnableCircuitBreaker //开启熔断
@EnableHystrixDashboard //开启Hystrix整合Dashboard
public class OpenFeignConsumer90Application {
public static void main(String[] args) {
SpringApplication.run(OpenFeignConsumer90Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
健康检查:
localhost:90/actuator/hystrix.stream
dashboard图形化界面:
点击 Monitor Stream :
这篇文章是本人的个人理解,不保证准确性,如果有错误的地方希望大家留言指正,一起学习共同进步!
如果转载请标明出处。谢谢