Hystrix 服务降级和服务熔断
说明:虽然该技术已近停更了,但是由于之后的一些服务降级技术和熔断机制都是借鉴了hystrix的设计理念,所以我们还是连接一下hystrix的设计思想和理念。 hystrix+eureka
1、问题的提出
服务雪崩
- 多个微服务之间调用的时候,当A调用B,B调用C,一级一级的往下调用,这就是所谓的"扇出",当该调用的链路上如果某个微服务响应时间过长或者直接不可用了,那么所有的服务就会卡在这里,占用大量的系统资源,最终资源全部占用系统崩溃,这就是“雪崩效应”。
- 对于高流量的应用来说,其占用了大量的系统资源,却又响应时间长,这也可能会导致其他的应用由于资源被占用了,本来流量少响应时间快,但因为大量资源被占用,所以可能导致延迟。
- 为了解决该类的问题所以就有了服务降级和熔断。
2、Hystrix
hystrix是一个处理分布式系统的延迟和容错的开源库,能够保障再出问题的时候还可以维持高可用性,不会导致整体服务的失败、避免级联故障、以提高分布式系统的弹性
基本设计的思想:当发生服务调用故障时,向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长期的等待或者抛出异常,这样就会保证了服务调用方法的线程不会被长期的、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
作用:
- 服务降级
- 服务熔断
- 服务限流
- 实时监控等等
3、服务降级
什么是服务降级
- 服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
- 通俗的讲就是当服务器繁忙时,不让服务继续在这里耗着,而是返回一个友好的提示【一个Fallback方法】,释放该线程的资源,减轻压力。
触发服务降级的情况
- 程序运行时异常 【即程序错误异常】
- 超时
- 服务熔断也会触发服务降级
- 线程池的最大线程数已满,无可再用线程
3.1 服务降级实操
- 为了方便管理包创建一个父工程
- pom.xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 创建一个子工程
- pom.xml
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!--eureka客户端-->
<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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.6.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.6.5</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- yaml
server:
port: 8003
spring:
application:
name: cloud-provide-payment
eureka: #注册中心
client:
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: payment8003
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2
lease-expiration-duration-in-seconds: 4
management:
endpoints:
web:
exposure:
include: "*"
- 主启动类
加上@EnableCircuitBreaker 开启服务降级和熔断功能的注解
package com.qiumin;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableEurekaClient //开启eureka客户端
@EnableCircuitBreaker //开启服务降级和熔断功能
public class payment8003Application {
public static void main(String[] args) {
SpringApplication.run(payment8003Application.class, args);
}
}
- service
@HystrixCommand @HystrixProperty 两个注解指定服务降级策略
package com.qiumin.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class paymentHystrixService {
//=========服务降级================
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_ok(Integer id){
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_ok,id: "+id+"\t"+"哈哈";
}
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "2000")
})
public String paymentInfo_TimeOut(Integer id){
try {
TimeUnit.MILLISECONDS.sleep(3000); //3000>2000即响应时间大于最久等待时间 触发服务降级
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"哈哈"+"耗时(毫秒)";
}
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOutHandler,id: "+id+"\t"+"服务降级!!!";
}
//====================================================================================================
//=========用于后面服务熔断测试================
@HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback",commandProperties = {
@HystrixProperty(name="circuitBreaker.enabled",value = "true"), //开启熔断器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //10次请求
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),//请求次数中有60%错误,熔断器打开
})
public String paymentCircuitBreaker(@PathVariable("id") int id){
if(id<0){
throw new RuntimeException("****不能为负数****");
}
String uuid = UUID.randomUUID().toString();
return Thread.currentThread().getName()+"\t"+"调用成功!!! 流水号为:"+uuid;
}
public String paymentCircuitBreakerFallback(@PathVariable("id") int id){
return "id 不能为负数,请稍后再试!!!"+id;
}
}
核心:为可能会出现超时或者异常的方法编写一个服务降级的方法并调用该方法 fallbackMethod =“兜底的方法” 。
注意:兜底的方法必须与其服务降级的方法,格式上必须一模一样,例如:返回值类型、参数类型等等。
- controller**
package com.qiumin.controller;
import com.qiumin.service.paymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class paymentHystrixController {
@Autowired
paymentHystrixService paymentHystrixService;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_ok(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_ok(id);
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
//用于服务熔断测试
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") int id){
return paymentHystrixService.paymentCircuitBreaker(id);
}
}
- 启动注册中心和该服务,测试即可
由于如果给每个方法的写一个兜底的方法会导致代码量的激增,所以可以定义一个默认的兜底的方法,如果没有个别定制兜底方法就用默认的兜底方法。
3.2 服务降级之客户端
客户端可以有自己的服务降级和熔断策略,
由于我们使用openfeign客户端远程调用,为了解耦和代码量,我们可以在客户端的接口上做服务降级
具体实现:
- 单独创建一个类实现客户端接口,重写里面的方法,即用于服务降级的方法。
- 有服务降级就调用该类中的对应的方法。
- 创建一个子工程客户端 【客户端调服务端】
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud2022</artifactId>
<groupId>com.qiu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-hystrix-openfeign-order8888</artifactId>
<properties>
<maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>15</maven.compiler.target>
</properties>
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!--开启openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--图形化处理依赖,与后面的图形化监控可用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.6.5</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.6.5</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- yaml
server:
port: 8888
spring:
application:
name: cloud-consumer-order
eureka:
client:
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka
feign:
circuitbreaker:
enabled: true #开启服务降级
- 客户端接口 【远程调用】**
package com.qiumin.client;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import com.qiumin.service.FallbackService;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
@FeignClient(value = "CLOUD-PROVIDE-PAYMENT",fallback = FallbackService.class) //fallback指向我们写的服务 降级需调用方法的类
public interface paymentClient {
@RequestMapping("/payment/query/{id}")
CommonResult<Payment> query(@PathVariable("id") int id);
@RequestMapping("/payment/create")
CommonResult<Payment> create(Payment payment);
}
- 服务降级的方法类 【用于服务降级】
package com.qiumin.service;
import com.qiumin.client.paymentClient;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import org.springframework.stereotype.Component;
@Component
public class FallbackService implements paymentClient {
@Override
public CommonResult<Payment> query(int id) {
return new CommonResult(444,"请求获取数据超时或错误,进行了服务降级操作!!!",null);
}
@Override
public CommonResult<Payment> create(Payment payment) {
return new CommonResult(444,"请求获取数据超时或错误,进行了服务降级操作!!!",null);
}
}
- controller
package com.qiumin.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.qiumin.client.paymentClient;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Slf4j
public class OrderController {
@Autowired
paymentClient paymentClient;
@RequestMapping("/consumer/payment/query/{id}")
@ResponseBody
public CommonResult<Payment> query(@PathVariable("id") int id){
log.info("进入了该方法!!");
return paymentClient.query(id);
}
}
- 测试即可
3.3 总结
- 加上@EnableCircuitBreaker 开启服务降级和熔断功能的注解
- @HystrixCommand @HystrixProperty 两个注解指定服务降级策略
- 核心:为可能会出现超时或者异常的方法编写一个服务降级的方法并调用该方法 fallbackMethod =“兜底的方法”
- 注意:兜底的方法必须与其服务降级的方法,格式上必须一模一样,例如:返回值类型、参数类型等等
- 注意:使用客户端时的controller里如果没有兜底的方法就不能有错误,因为我们的服务降级是配置在了feignclient上面的,没有处理controller里的,需我们另外处理
4、服务熔断
什么是服务熔断,【类似保险丝】
- 服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
- 服务熔断也会触发服务降级,服务熔断会直接暂时拒绝请求,然后调用服务降级方法返回友好提示。
服务熔断机制:当出现故障时,在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。,熔断一旦开启,这时就会拒绝请求包括正确的请求,直接执行本地的默认方法,达到服务降级的效果,熔断不会一直开启,熔断后一段时间,会继续放一批请求,这是处于半熔断状态,当进去的请求得到成功执行时,这时会认为服务故障已经清除,熔断就会关闭,服务正常进行。
Hystrix设计了三种状态:
- 熔断关闭状态(Closed):服务正常进行,不受限制。
- 熔断开启状态(Open): 在固定时间内(Hystrix默认是10秒),接口调用出错比率达到一个阈值(Hystrix默认为50%),会进入熔断开启状态。这时就会拒绝所有服务,调用服务降级方法。
- 半熔断状态(Half-Open): 在进入熔断开启状态一段时间之后(Hystrix默认是5秒),熔断器会进入半熔断状态。该状态下会放一批请求,监控该批请求的正确率,如果成功率达到预约,就认为服务恢复正常,关闭熔断状态。
图示:
涉及到熔断器三个重要的参数: 快照时间窗
、 请求总数阈值
、错误百分比阈值
- **快照时间窗:**断路器确定是否打开需要统计的请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近10秒。
- **请求总数阈值:**在快照时间窗内,必须满足请求总数阈值才可能发送熔断,默认20,意味着10秒内调用次数不足20次,即使所有请求的失败也不会触发服务熔断。
- **错误百分比阈值:**满足以上的前提下,请求错误率达到默认阈值50%就会触发服务熔断。
- 测试见服务降级处的代码
qiumin