更新时间:2020/10/01 18:04,更新到了Hystrix
本文主要对springcloud中的服务降级进行学习与记录,主要偏向于实战,本文会持续更新,不断地扩充
本文仅为记录学习轨迹,如有侵权,联系删除
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C有调用其他的微服务,这就是所谓的”扇出”,如扇出的链路上某个微服务的调用响应式过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统雪崩,所谓的”雪崩效应”
服务降级
服务降级是指 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务。
简单理解就是在服务器压力大的情况下,先停掉一些非核心的服务,直接返回错误信息,把资源让给核心的服务,这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
服务熔断
服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
简单理解就是当服务链路的某一个微服务不可用的情况下,直接停止该节点服务的调用,快速返回错误的信息
服务限流
限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。
简单理解就是当有大量的请求打进服务的时候,服务检测到这样大的流量,并且对流量进行限制
服务熔断和服务降级的区别
服务熔断 | 服务降级 |
---|---|
服务熔断一般是某个服务(下游服务)故障引起 | 服务降级一般是从整体负荷考虑 |
依赖的下游服务故障触发熔断,避免引发本系统崩溃;系统自动执行和恢复 | 服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑; |
Hystrix是Netflix开源的一款容错框架,包含常用的容错方法:线程池隔离、信号量隔离、熔断、降级回退。在高并发访问下,系统所依赖的服务的稳定性对系统的影响非常大,依赖有很多不可控的因素,比如网络连接变慢,资源突然繁忙,暂时不可用,服务脱机等。我们要构建稳定、可靠的分布式系统,就必须要有这样一套容错方法。
下面开始实战
pom
‘先创建一个支付服务模块,命名为cloud-provider-hystrix-payment8003,里面引入坐标依赖如下
<dependencies>
<!--引入公共包-->
<dependency>
<groupId>com.zsc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--springboot starter启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
主要是eureka坐标依赖和hystrix坐标依赖,然后是主启动类的注解添加,主要添加两个,一个用于Eureka,一个用于Hystrix激活
application.yml
创建yml配置文件进行配置
server:
port: 8003
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
#将自己注册进eureka服务中心
register-with-eureka: true
fetch-registry: true
#对应的eureka服务端的地址
service-url:
#集群的情况下,服务的入住
#defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7002.com:7002/eureka/
# 单机版
defaultZone: http://eureka7001.com:7001/eureka/
instance:
#actuator微服务信息完善,修改该服务的名称
instance-id: payment-hystrix8003
#访问路径可以显示ip地址
prefer-ip-address: true
service层
然后是服务层的方法定义,在这一层上将会对一些方法进行降级处理,这里先简单讲一下什么样的条件下会触发降级处理,一个是响应的时间超过设置好的时间就会触发降级处理,这样可以避免长时间的响应导致资源被拖死,还有一个是出现异常会触发降级处理
这里定义了一个方法paymentInfoTimeout,并且通过@HystrixCommand注解添加了降级处理,设置了最长能等待的响应时间为2秒,一旦响应的时间超过2秒就会跳到对应的降级方法。同时异常也会触发降级方法
这样会有一个问题,如果每个方法都做降级处理的话,会有大量的代码冗余,于是出现了全局降级处理,通过在服务层对应的类上加一个注解@DefaultProperties,这样,里面的所有的方法都会有对应的全局降级方法,如果是特定的方法需要特殊处理,也可以用上面@HystrixCommand注解进行特殊处理
下面给出服务层的全部代码
@Service
@DefaultProperties(defaultFallback = "paymentGlobalFallback")
public class PaymentService {
@HystrixCommand(fallbackMethod = "paymentInfoTimeoutHandler",commandProperties = {
//表示如果调用paymentInfoTimeout方法,响应的时间超出2000毫秒(2s)就会触发降级方法,
// 跳转到paymentInfoTimeoutHandler方法
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
})
public String paymentInfoTimeout(Integer id){
/**
* 异常也会触发降级方法
*/
//int a = 10/0;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+", this is paymentInfoTimeout: "+id+",没想到吧,老子花了3秒时间!!!";
}
@HystrixCommand//默认全局降级方法
public String paymentInfoOk(Integer id){
return "线程池:"+Thread.currentThread().getName()+", this is paymentInfoOk: "+id.toString();
}
@HystrixCommand//默认全局降级方法
public String paymentInfoException(){
int a = 10/0;
return "this is runtimeException";
}
@HystrixCommand//默认全局降级方法
public String hello(){
return "this is hello";
}
/**
* 全局的降级方法
* @return
*/
public String paymentGlobalFallback(){
return "this is paymentGlobalFallback(全局的降级方法)";
}
/**
* paymentInfoTimeout特定的降级方法
* @param id
* @return
*/
public String paymentInfoTimeoutHandler(Integer id){
return "this is paymentInfoTimeoutHandler(paymentInfoTimeout对应的降级方法)";
}
}
controller层
控制层直接调用服务层接口,没什么好说的
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@GetMapping("/paymentInfoOk/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id){
String result = paymentService.paymentInfoOk(id);
System.out.println("*****************result = " + result);
return result;
}
@GetMapping("/paymentInfoTimeout/{id}")
public String paymentInfoTimeout(@PathVariable("id") Integer id){
String result = paymentService.paymentInfoTimeout(id);
System.out.println("*****************result = " + result);
return result;
}
@GetMapping("/paymentInfoException")
public String paymentInfoException(){
String s = paymentService.paymentInfoException();
return s;
}
@GetMapping("/hello")
public String hello(){
String hello = paymentService.hello();
return hello;
}
}
运行项目进行测试,这里测试服务降级,可以不用开eureka服务端
对于服务端的服务降级也可以用服务端降级的方式进行降级处理,但一般不那么做,而是用面向接口的方式进行服务降级
创建客户端模块
创建客户端(消费者)模块cloud-consumer-openfeign-hystrix-order80,用于调用上面的服务端,同样pom引入坐标
<dependencies>
<!--引入公共包-->
<dependency>
<groupId>com.zsc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--springboot starter启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
客户端要调用服务,要用到openfeign,所以这里重点引入openfeign、eureka和hystrix坐标依赖,然后配置yml文件
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-hystrix-order80
eureka:
client:
#将自己注册进eureka服务中心
register-with-eureka: true
fetch-registry: true
service-url:
#集群的情况下,服务的入住
defaultZone: http://eureka7001.com:7001/eureka/
ribbon:
#建立连接后从服务器读取到的可用资源所用的时间
ReadTimeout: 5000
#建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ConnectTimeout: 5000
#开启hystrix
feign:
hystrix:
enabled: true
重要的一点,主启动类添加上对应的注解
服务调用
服务的调用使用openfeign进行调用
controller层
这个在服务调用篇章说过了,就不过多解释了
服务降级
客户端的服务降级虽然可以采用跟服务端的降级方式,但一般不用,而是用新建一个类,并且继承service层的接口,在里面实现降级方法,使用的时候用@FeignClient注解调用即可
@FeignClient里面的fallback属性表示,如果客户端调用服务端,由于各种原因客户端没有拿到响应的数据,就会触发降级方法,通过fallback标注的类,找同名的实现类方法作为降级方法,下面给出这两个的代码
//value是要调用的哪一个服务,fallback是服务降级触发的降级方法
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = OpenfeignHystrixServiceFallback.class)
@Service
public interface OpenfeignHystrixService {
@GetMapping("/payment/paymentInfoOk/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id);
@GetMapping("/payment/paymentInfoTimeout/{id}")
public String paymentInfoTimeout(@PathVariable("id") Integer id);
@GetMapping("/payment/paymentInfoException")
public String paymentInfoException();
@GetMapping("/payment/hello")
public String hello();
}
@Component
public class OpenfeignHystrixServiceFallback implements OpenfeignHystrixService {
@Override
public String paymentInfoOk(Integer id) {
return "(客户端)this is paymentInfoOk 降级方法";
}
@Override
public String paymentInfoTimeout(Integer id) {
return "(客户端)this is paymentInfoTimeout 降级方法";
}
@Override
public String paymentInfoException() {
return "(客户端)this is paymentInfoException 降级方法";
}
@Override
public String hello() {
return "(客户端)this is hello 降级方法";
}
}
然后是controller层的接口定义,这个就正常写接口即可
启动项目,由于这次客户端调用服务端,所以需要启动eureka服务端注册中心
服务熔断的概念这里就再重复,上面有,这里直接进入实战,还是再这个项目上进行服务熔断处理,在服务层再添加一个方法如下
这里主要讲解这个方法上的注解
这个注解上面开启了熔断机制,意思就如图上面所说的开启了断路器后,当10秒内,5个请求的进来后,失败率达到60%以上的时候触发熔断机制,触发后短时间内该方法不可用,当又有请求进来后,成功请求次数越来越多的时候会恢复该链路,即链路恢复并且上面还有fallback服务降级处理,可以这样理解,当达到熔断条件后,先进行服务降级,触发降级方法,然后服务熔断,之后服务再慢慢恢复
控制层写一个接口测试一下熔断机制
启动项目
总结
断路器的打开和关闭,是按照一下5步决定的
1,并发此时是否达到我们指定的阈值
2,错误百分比,比如我们配置了60%,那么如果并发请求中,10次有6次是失败的,就开启断路器
3,上面的条件符合,断路器改变状态为open(开启)
4,这个服务的断路器开启,所有请求无法访问
5,在我们的时间窗口期,期间,尝试让一些请求通过(半开状态),如果请求还是失败,证明断路器还是开启状态,服务没有恢复
如果请求成功了,证明服务已经恢复,断路器状态变为close关闭状态
当微服务越来越多的情况下,就必须要对这些服务进行监控,下面直接进行服务的监控
创建流量监控模块cloud-hystrixdashboard-9001,引入pom坐标
<dependencies>
<!--引入公共包-->
<dependency>
<groupId>com.zsc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--springboot starter启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
重点是hystrix dashboard仪表盘依赖,简单配置一下该项目的端口号
主启动类上添加注解
启动该项目
添加服务监控
要监控哪个服务就在哪个服务里面引入actuator依赖,这里以上面创建的cloud-provider-hystrix-payment8003项目为例,监控该项目
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
然后在cloud-provider-hystrix-payment8003项目的主启动类中添加监控配置
/**
* 此配置是为了服务监控而配置的,与服务容错本身无关,Springboot升级后留下的坑
* @return
*/
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet hystrixMetricsStreamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(hystrixMetricsStreamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
两个项目启动后,访问http://localhost:9001/hystrix,并且在那里数据要监控的服务路径进行监控
对该仪表盘的解读