SpringCloud实战---第十四篇-Ⅰ:Hystrix概念及快速上手
系列文章目录
SpringCloud快速入门到精通各组件原理
专栏传送门
@TOC
前言
OpenFeign也太好用了吧!!!
说起来容易做起来难,一步一步都干完!!!
学习一定要自己动手搞一搞,不能只眼会。
学习笔记是跟着尚硅谷的视频学的:传送门
一、关于Hystrix–豪猪哥
- 关于服务降级、服务熔断、服务限流、服务隔离等等一系列优秀的设计理念,出道即巅峰,虽然现在已经停止维护,现在的相关框架都是对豪猪哥的抄作业,所以仍然具有很高的学习价值。
- 微服务解决了模块解耦,但是会出现链路变长(A调用B,B调用C···),这样当B或C有一个出现问题时,A都会失败,然后A、B、C的频繁失败会占用越来越多的资源,最终导致雪崩,系统崩溃。为了解决这一问题,服务降级和服务熔断产生。
- Hystrix主要用于保护其他服务,当链路上某一服务出现问题时,即使进行阻断或延迟或放行,不要让他影响其他服务。
- 官方说明:在github上面搜索,查看开源社区。
- 服务降级fallBack:当服务出现不可用,不要直接抛出错误给调用方,而是返回一个符合调用方预期格式的信息。
- 服务熔断break:相当于是保险丝,当出现服务处理崩溃的边缘时,直接拒绝访问,然后调用服务降级方法返回符合预期格式的信息。
- 服务限流flowlimit:当流量达到某一值时,为了防止服务崩溃,进行服务器限流,进来的服务排队进行或直接拒绝提示服务器忙。
- 豪猪哥通过一系列操作主要目的是出现故障时保护服务调用方的线程不被长时间占用。
二、先构建好基础工程(一篇一篇看过来的不用重新构建)
构建基础父工程:构建基础父工程
Rest风格微服务:Rest风格微服务
传统分布式方法:传统分布式方法
改造工程,抽取公共模块:改造工程,抽取公共模块
使用Eureka:使用Eureka
Eureka集群: Eureka集群
想偷懒的请下载;gitee上我上传的代码 : 懒人传送门
基础工程构建完成的目录结构:
启动所有模块,访问
localhost:7001
显示如下,代表基础工程没问题
话不多说,立马开干
三、工程改造
1. 为了学习方便,我们先把7001改成单节点,将7001作为注册中心
修改7001的配置文件
defaultZone: http://eureka7001.com:7001/eureka/
2. 构建带有Hystrix熔断的8001
模块名
cloud-provider-hystrix-payment8001
包名
com.atguigu.springcloud
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>cloud2021</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-hystrix-payment8001</artifactId>
<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>
</project>
application.yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
启动类PaymentHystrixMain8001
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @Author: Daisen.Z
* @Date: 2021/12/27 11:42
* @Version: 1.0
* @Description:
*/
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
3. 编写业务类
我们编写一个一定正确的接口,再编写一个有可能出错的接口,用于学习测试。
PaymentService:
package com.atguigu.springcloud.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @Author: Daisen.Z
* @Date: 2021/12/27 11:46
* @Version: 1.0
* @Description:
*/
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"O(∩_∩)O";
}
public String paymentInfo_TimeOut(Integer id){
int timeNumber =3;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"O(∩_∩)O 耗时"+timeNumber+"秒钟";
}
}
PaymentController,调用service,模拟一个正常的,一个出现超时异常的:
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @Author: Daisen.Z
* @Date: 2021/12/27 11:57
* @Version: 1.0
* @Description:
*/
@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("********** result:"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("********** result:"+result);
return result;
}
}
4. 启动测试
启动7001和8001,访问接口,测试编写的model是否正常。
http://localhost:8001/payment/hystrix/ok/1
http://localhost:8001/payment/hystrix/timeout/1
量接口都正常,证明我们的model是没有问题的。
四、演示高并发场景下对服务的影响
我们编写好的两个接口在非高并发情况下访问正常没有问题,但是设想一下,高并发情况下,那个sleep3秒的接口肯定会报资源不足异常(线程资源得不到释放),因为servlet是多线程访问的,每个线程占三秒,高并发场景下很快程序就会崩溃。
接下来我们测试一下。
1. 安装压力测试工具Jmeter
2. 添加测试线程
添加一个线程组
按照如下配置,表示1秒内向接口发送200*100个请求
crtl+s,保存一下
3. 添加测试HTTP请求
填写信息,将两万个请求打到/payment/hystrix/timeout/{id}接口上,保存
4. 启动
5. 测试接口
看后台,发现发送的两万个请求还在处理中(因为一个要3秒)
我们再访问我们刚才很快能响应的那个接口
http://localhost:8001/payment/hystrix/ok/1
我们会发现,有时这个接口也会转圈,说明timeout那个接口的资源占用产生了影响
当然,虽然现在转圈还是很快,但是再真正的高并发场景下可不止两万这么简单哦!
五、演示雪崩
看热闹不嫌事大,我们新建一个80端口的服务调用方,来调用hystrix-8001模块的接口,然后测试下8001高并发场景下,对80产生的雪崩效应,然后我们再使用hystrix进行服务降级处理,来解决雪崩问题。
1. 新建微服务调用方hystrix-80
模块名:
cloud-consumer-feign-hystrix-order80
pom:
<?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>cloud2021</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-hystrix-order80</artifactId>
<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>
</project>
application.yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
基础包:
com.atguigu.springcloud
启动类OrderHystrixMain80
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @Author: Daisen.Z
* @Date: 2021/12/27 16:11
* @Version: 1.0
* @Description:
*/
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
Feign接口PaymentHystrixService
package com.atguigu.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @Author: Daisen.Z
* @Date: 2021/12/27 16:13
* @Version: 1.0
* @Description:
*/
@FeignClient(value = "cloud-provider-hystrix-payment")
@Component
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
测试接口OrderHystrixController
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @Author: Daisen.Z
* @Date: 2021/12/27 16:15
* @Version: 1.0
* @Description:
*/
@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);
}
}
2. 启动服务调用方hystrix-80,测试模块是否正常
启动7001,hystrix-8001,hystrix-80模块,进行自测访问
首先是8001接口自测
ok的接口,很快就能出结果
http://localhost:8001/payment/hystrix/ok/1
timeout的接口,大约三秒钟左右出结果
http://localhost:8001/payment/hystrix/timeout/1
然后是80服务调用方测试
ok的接口,很快能访问
http://localhost/consumer/payment/hystrix/ok/1
timeout的接口,出现超时异常(Feign调用默认超时时间1秒)
http://localhost/consumer/payment/hystrix/timeout/1
注意, 这时只有服务调用方的timeout接口调用时会出问题,ok接口是没有问题的,接下来我们把两万个请求打到8001的timeout接口上,再测试一下
3. 测试服务雪崩
启动Jmeter
访问刚才80的ok接口
http://localhost/consumer/payment/hystrix/ok/1
会发现要么很慢,要么直接超时
注意,我们压力测试时打的时timeout接口,然后timeout接口处理不过来请求了,服务提供方8001资源不足,影响了我们调用的ok接口,ok接口也不能用了,服务出现了整体的不可用。
六、Hystrix闪亮登场
1. 8001服务方的服务降级
当服务方8001自己请求处理不过来,出现超时时,应该有适当的容错降级,不要把错误暴漏给调用方,而是返回一个调用方可以处理的消息。
改造8001工程的Service,使用@HystrixCommand注解,声明出出现异常时调用哪个方法
// 方法出异常时会找fallbackMethod配置的方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000") // 表示这个线程的超时时间时3秒钟,下面我们线程睡了5秒,他一定会超时
})
public String paymentInfo_TimeOut(Integer id){
int timeNumber =5;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id:"+id+"\t"+"O(∩_∩)O 耗时"+timeNumber+"秒钟";
}
public String paymentInfo_TimeOutHandler(Integer id){
int timeNumber =3;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池"+Thread.currentThread().getName()+" paymentInfo_TimeOutHandler,id:"+id+"\t"+"/(ㄒoㄒ)/~~处理不过来了";
}
给主启动类添加激活信息
@EnableCircuitBreaker // 开启回注
2. 测试8001超时降级
启动7001和8001服务,访问
http://localhost:8001/payment/hystrix/timeout/1
等了三秒左右,页面上就会返回我们降级方法的返回值
并且我们发现了打印的线程名是一个Hystrix的线程,这说明降级不会影响我们的请求,而是一个新的线程,做到了线程隔离。
3. 80服务调用方的降级
80服务调用方再调用接口不通时,也需要有自我保护而不是应该报错,通常我们在业务逻辑处理时都是把服务降级做在调用方,以实现一种自我保护。
修改80的application.yml
feign:
hystrix:
enabled: true
主启动类上声明激活Hystrix
@EnableHystrix
把8001的 timeout改为正常的,不会在8001上降级
修改80的Controller,添加上降级的回调方法
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// 方法出异常时会找fallbackMethod配置的方法
@HystrixCommand(fallbackMethod = "paymentInfoTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500") // 表示这个线程的超时时间时3秒钟,下面我们线程睡了5秒,他一定会超时
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String paymentInfoTimeOutFallbackMethod(Integer id){
return "80端:8001支付服务繁忙,请10秒后重试,如未解决请联系开发人员排查自身异常。。。";
}
4. 启动测试
启动7001、hystrix-8001、hystrix-80
访问
http://localhost/consumer/payment/hystrix/timeout/1
服务已经进行降级,这样就实现了及时的阻断服务异常,不让异常传递
总结
- hystrix通过服务降级来实现的解决异常传递问题。
- 服务降级可以这么理解,类似于淘宝,淘宝的支付宝功能出现了问题,支付功能不能用了,如果我们没有错误降级,在淘宝上调用支付时每次都会报出异常,然后大量的用户使用淘宝调用支付,就会导致资源占用,淘宝直接崩溃,使用了降级后,我们再调用时就直接返回一个友好的提示(系统繁忙,请稍后重试),至少能够保障系统的正常运行。
- hystrix的服务降级可以使用再服务调用方和服务提供方,通常服务调用方需要做降级处理。
- hystrix的降级是有一个回调方法,当出现异常时调用配置的回调方法返回预先设置好的符合接口格式的信息,防止异常传递。
- @EnableCircuitBreaker加在主启动类上,用于开启回注
- 调用方启用hystrix需要在配置文件开启,在主启动类上添加@EnableHystrix启用
- @HystrixCommand注解加在方法上,配置的是降级时调用哪个方法及降级触发的条件
- 点赞
- 收藏
- 关注作者
评论(0)