文章目录
一、官方文档
二、服务雪崩
分布式系统面临的问题:复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免失败!
服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用响应时间过长,或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几十秒内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以达到单个依赖关系的失败而不影响整个应用程序或系统运行。
我们需要,弃车保帅!
三、什么是Hystrix?
Hystrix是一个应用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整个体系服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控 (类似熔断保险丝) ,向调用方返回一个服务预期的,可处理的备选响应 (FallBack) ,而不是长时间的等待或者抛出调用方无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
四、Hystrix能干嘛?
- 服务降级
- 服务熔断
- 服务限流 (这个我们这里不讲)
- 接近实时的监控
- …
五、服务降级
1、出现场景
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满
当出现以上情况时,为了不让客户端等待过长时间而导致后续功能的异常,直接返回一个友好提示,我们这里称之为fallback。
2、模拟高并发场景下的异常
(1)服务端-项目构建
- 创建maven工程
- pom文件
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<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>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- application.yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
#是否将自己注册进eurekaServer默认true
register-with-eureka: true
#是否从eurekaServer抓取已有的注册信息,默认true。
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
- 主启动类
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain.class,args);
}
}
- 业务层
@Service
public class PaymentService {
//正常访问返回的方法
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK, id: "+id+" ==》正常返回";
}
//耗时等待返回的方法
public String paymentInfo_TimeOut(Integer id) throws InterruptedException {
int timeOut = 3;
TimeUnit.SECONDS.sleep(timeOut);
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut, id: "+id+" ==》耗时等待"+timeOut+"秒";
}
}
- 控制层
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("paymentInfo_OK 响应结果:"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException {
String result = paymentService.paymentInfo_TimeOut(id);
log.info("paymentInfo_TimeOut 响应结果:"+result);
return result;
}
}
- 测试
访问正常返回方法(立刻返回):
访问睡眠3秒钟再返回方法(转圈等待3秒):
(2)Jmeter高并发访问
我们通过Jmeter压测工具来模拟高并发的场景,这里我们通过2000个并发访问paymentInfo_TimeOut,每次请求都需要睡眠3秒钟才能返回。
新建线程组:
建立HTTP请求:
后台一直请求:
此时访问paymentInfo_OK方法(以前立刻秒回)也在转圈等待!!!!!
原因在于20000个请求一直发送到paymentInfo_TimeOut,tomcat工作线程被使用完了,没有多余的线程来处理paymentInfo_OK,此时访问paymentInfo_OK也需要等3秒钟(等其中访问paymentInfo_TimeOut的线程执行完)。
(3)消费端-项目构建
- 创建maven工程
- pom文件
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- application.yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
- 主启动类
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain.class, args);
}
}
- 业务层
利用Feign调用客户端微服务方法
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
- 控制层
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService PaymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
return PaymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return PaymentHystrixService.paymentInfo_TimeOut(id);
}
}
当服务端20000个并发访问paymentInfo_TimeOut,此时消费者发送请求,无论哪里方法,都需要等待3秒。服务端发送请求出现的问题一致。
六、服务降级的处理方式
服务降级:当消费端发送请求到客户端,客户端出现超时、异常、宕机,此时需要有兜底处理方式,不至于整个服务崩溃。
- 服务出现问题,则将请求转到默认处理返回方法(友好提示)
- 客户端和消费端都可以做,一般放在消费端
1、消费端设置服务降级
启动类:
application.yml
控制层
消费者调用paymentInfo_OK方法(有10/0异常),此时后台无报错信息,相当于直接跳转方法了
消费者调用paymentInfo_TimeOut方法(睡眠3秒,2秒超时)
结论和注意点:
- @DefaultProperties注解为全局降级方法,对于添加@HystrixCommand注解的方法会走全局公共降级方法
- @HystrixCommand(fallbackMethod = “xxx” )则为当前方法设置专属的降级方法
- 注意事项1:设置专属降级方法时候,其降级方法入参必须与注解方法一致,否则报错找不到指定参数的降级方法
- 注意事项2:如果代码中有异常,比如10/0,不会抛错,只会走降级方法
2、消费端解耦式设置服务降级(不在Controller业务层)
业务层
@RestController
@Slf4j
//@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixService PaymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
int i = 10/0;
return PaymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return PaymentHystrixService.paymentInfo_TimeOut(id);
}
}
为Feigin添加fallback类,此类实现本接口,每个实现类即为调用服务的降级方法
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
降级方法
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "PaymentFallbackService 的 paymentInfo_OK的降级方法";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "PaymentFallbackService 的 paymentInfo_TimeOut的降级方法";
}
}
访问paymentInfo_OK方法,消费者中有10/0
访问paymentInfo_TimeOut方法,超时
3、两种方式的区别
- 在Controller层添加降级方法相当于为消费端和服务端所有代码添加降级方法,无论哪里出现错误,都会进入降级方法
- 利用feign设置fallback降级方法,相当于为服务端添加降级方法,只有服务端出现异常才会进入降级方法,消费端的异常会将错误抛出,并不会进入降级方法
七、服务熔断
1、什么是服务熔断
熔断机制是对雪崩效应的一种微服务链路保护机制。
我们在家里有时候电压或者电流过大,保险丝会跳闸限电,任何电器都暂时无法使用,这样可以阻止长时间使用造成更大的事故。此时的我们为了能正常使用,会拔掉一些用电量大的电器,然后重新开闸使用。
在程序中也是如此,当服务熔断后,直接拒绝访问,拉闸限电,后续的任何请求都会调用服务降级的方法并返回友好提示。与家用保险丝不同的是,这个熔断机制会自己隔段时间检测,当某短时间内某个请求能够正常访问,则服务熔断自动关闭,反之则继续服务熔断。
服务熔断解决如下问题:
- 当所依赖的对象不稳定时,能够起到快速失败的目的;
- 快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复。
2、熔断机制的三种状态
- 熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
- 熔断开启状态(Open)
在固定时间内(Hystrix默认是10秒),接口调用出错比率达到一个阈值(Hystrix默认为50%),会进入熔断开 启状态。进入熔断状态后, 后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法。
- 半熔断状态(Half-Open)
在进入熔断开启状态一段时间之后(Hystrix默认是5秒),熔断器会进入半熔断状态。所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。
如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态。
三者关系图:
八、服务熔断的处理方式
这里模拟消费端的处理情况,现实开发中会在服务端写服务熔断方法,但是调用的服务降级方法是在消费端写,通过OpenFeign实现编写服务降级的接口。
1、启动类
2、service层
@Service
public class PaymentService {
//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失败率达到多少后跳闸
})
public String paymentCircuitBreaker(Integer id){
if (id < 0){
throw new RuntimeException("*****id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(Integer id){
return "id 不能负数,请稍候再试,(┬_┬)/~~ id: " +id;
}
}
3、controller层
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
//===服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("*******result:"+result);
return result;
}
}
4、测试
-
单次请求传入正数,服务正常返回
-
单次请求传入负数,走服务降级方法
-
多次请求传入负数,一直走服务降级方法,此时服务熔断打开,传入正数访问。
正数也走服务降级方法,因为此时服务熔断打开,微服务不可访问
5、断路器开启和关闭的条件
此处代码的开启条件(必须都满足):
- 当满足一定的阀值(默认10秒内超过20个请求)–代码设置10秒内超过10个请求
- 当失败率达到一定的时候(默认10秒内超过50%的请求失败)–代码设置10秒内超过60%的请求失败
关闭条件
- 当开启的时候,所有请求不会进行转发
- 开启以后,一段时间之后(默认5秒),这个时候断路器是半开状态,会让其中一个请求进行转发,如果成功,断路器会关闭,若失败,继续开启。–代码默认10秒
详细配置参考官方文档:断路器配置
九、服务熔断和服务降级的区别
-
服务降级:不管在什么情况下,服务降级的流程都是先调用正常的方法,再调用fallback的方法。 也就是服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个友好提示。
-
服务熔断:假设服务宕机或者在单位时间内调用服务失败的次数过多,即服务降级的次数太多,那么则服务熔断。 并且熔断以后会跳过正常的方法,会直接调用fallback方法,即所谓“服务熔断后不可用”。 类似于家里常见的保险丝,当达到最大服务访问后,会直接拒绝访问,拉闸限电,然后调用服务降级的fallback方法,返回友好提示。
十、Dashboard 流监控
1、新建maven工程
2、pom文件
<!--dashboard依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
3、application.yml
server:
port: 9001
4、主启动类
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001
{
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class, args);
}
}
5、修改cloud-provider-hystrix-payment8001模块
在主启动类下添加如下代码:
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001
{
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
//访问该页面就是监控页面
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
6、测试
访问:http://localhost:9001/hystrix
进入监控页面:
效果如下图:
页面解释: