SpringCloud整合Hystrix服务保护框架
1 引入Hystrix需要了解的知识点
1.1 服务的雪崩效应
服务的雪崩效应:默认情况下,tomcat只有一个线程池去处理所有客户端发生的所有请求,这样在高并发下的情况下,
如果客户端所有的请求堆积到同一个接口上(也就是客户端访问同一个接口),tomcat的所有线程去处理请求,可能
会导致其他服务接口无法访问。
假设tomcat最大请求数为20,客户端发送的请求数为100,会发生80个请求产生延迟等待
1.2 Hystrix服务保护框架能解决什么问题
- 断路器
- 服务降级
- 服务熔断
- 服务隔离机制
服务雪崩效应,连环雪崩效应
注意:如果雪崩效应严重的话,可能会造成整个服务崩溃
1.3 Hystrix解决雪崩效应的原理
服务降级 —-友好提示
在高并发的情况下,防止用户一直等待,在tomcat中没有线程处理客户端发生的请求时,不应该让客户端一直在等待, 使用服务降级(返回一个友好的提示给客户端),目的是为了用户的体验,调用fallback本地方法
服务隔离 —-线程池隔离和信号量隔离
信号量的使用示意图:当n个并发请求去调用一个目标服务接口时,都要获取一个信号量才能真正去调用 目标服务接口,但信号量有限,默认是10个,可以使用maxConcurrentRequests参数配置,如果并发请求数多于 信号量个数,就有线程需要进入队列排队,但排队队列也有上限,默认是 5,如果排队队列也满,则必定有请求线 程会走fallback流程,从而达到限流和防止雪崩的目的。 信号量模式从始至终都只有请求线程自身,是同步调用模式,不支持超时调用,不支持直接熔断,由于没有线程的 切换,开销非常小。 线程池的使用示意图如下图所示,当n个请求线程并发对某个接口请求调用时,会先从hystrix管理的线程池里面 获得一个线程,然后将参数传递给这个线程去执行真正调用。线程池的大小有限,默认是10个线程,可以使用 maxConcurrentRequests参数配置,如果并发请求数多于线程池线程个数,就有线程需要进入队列排队,但排队 队列也有上限,默认是 5,如果排队队列也满,则必定有请求线程会走fallback流程。 线程池模式可以支持异步调用,支持超时调用,支持直接熔断,存在线程切换,开销大。
服务熔断
服务熔断目的是为了保护服务,在高并发的情况下,如果请求达到一定得极限(可以设置阈值),如果流量超过 了这是的阈值,自动开启保护服务的功能,使用服务降级方式返回一个友好的提示。服务熔断和服务降级一起使用
2 搭建Hystrix服务保护框架
2.1 feign客户端超时设置
首先了解feign客户端默认超时时间是1秒
ribbon:
# 建立连接所用的时间
ReadTimeout: 5000
# 建立之后读取所用的时间
ConnectTimeout: 5000
2.3 开启hystrix断路器
feign.hystrix.enabled=true
2.4 在已经创建的微服务项目中整合Hystrix框架
2.4.1 添加hystrix的依赖
<!-- 导入springcloud整合hystrix启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.4.2 hystrix整合的两种方式
修改订单服务配置文件
# 项目的端口号
server.port=8001
# 服务别名--服务注册到注册中心的名称
spring.application.name=app-test-order
# 当前服务注册到eureka服务地址
eureka.client.service-url.defaultZone=http://localhost:8100/eureka
# 需要将服务注册到eureka上面
eureka.client.register-with-eureka=true
# 是否需要检索服务
eureka.client.fetch-registry=true
spring.main.allow-bean-definition-overriding=true
#feign客户端建立连接超时时间
feign.client.config.default.connect-timeout=10000
#feign客户端建立连接后读取资源超时时间
feign.client.config.default.read-timeout=10000
#开启Hystrix断路器
feign.hystrix.enabled=true
#配置Hystrix 超时时间 超时关闭
#hystrix.command.default.execution.timeout.enabled=false
#超时时间(默认1000ms)在调用方配置,被该调用方的所有方法的超时时间都是该值,优先级低于下边的指定配置
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
#在调用方配置,被该调用方的指定方法(HystrixCommandKey方法名)的超时时间是该值
hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds=4000
#线程池核心线程数 默认为10
hystrix.threadpool.default.coreSize=10
#最大排队长度。默认-1 如果要从-1换成其他值则需重启,即该值不能动态调整,若要动态调整,需要使用到下边这个配置
hystrix.threadpool.default.maxQueueSize=100
#排队线程数量阈值,默认为5,达到时拒绝,如果配置了该选项,队列的大小是该队列
hystrix.threadpool.default.queueSizeRejectionThreshold=5
# 简言之,10s内请求失败数量达到20个,断路器开。 当在配置时间窗口内达到此数量的失败后,进行短路。默认20个
hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
#短路多久以后开始尝试是否恢复,默认5s
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5
#出错百分比阈值,当达到此阈值后,开始短路。默认50%
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50%
#调用线程允许请求HystrixCommand.GetFallback()的最大数量,默认10。超出时将会有异常抛出,注意:该项配置对于THREAD隔离模式也起作用
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests=50000
方法一:采用新建fallbackMethod指定服务降级的方式
1.使用@HystrixCommand注解指定fallbackMethod方法
/**
* 解决服务的雪崩效应
* @HystrixCommand:默认开启服务隔离方式(以线程池方式)、服务降级、熔断机制
*
* Hystrix提供两种方式解决服务的雪崩效应
* 方法一:自定义方法
* */
@RequestMapping("orderToMemberHystrix")
@HystrixCommand(fallbackMethod = "getFallBack")
public Map<String,Object> orderToMemberHystrix(){
Map<String,Object> map = new HashMap<>();
System.out.println("orderToMemberHystrix :线程池名称 "+Thread.currentThread().getName());
String member = memberFeign.getMember("张三");
map.put("name",member);
map.put("state","200");
return map;
}
public Map<String,Object> getFallBack(){
Map<String,Object> map = new HashMap<>();
map.put("msg","返回一个友好的提示:服务器忙,请稍后提示");
map.put("state","400");
return map;
}
注意:这里需要明确几个点
1. @HystrixCommand注解默认开启服务隔离(以线程池方式)、服务降级、服务熔断机制
2. fallbackMethod指定的方法的返回值必须与@HystrixCommand注解标识的方法的返回值一致
2. 在订单服务的启动类上开启Hystrix并启动Eureka服务、会员服务、订单服务
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/23 10:18
* @description:TODO
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class,args);
}
}
3.使用工具Apache Jemeter进行测试
1). 首先指定线程组每次执行多少条请求,以及循环多少次
2). 指定访问的路径并启动http:localhost:8001/orderToMemberHystrix
3). 在该工具启动之后再次在浏览器端访问http:localhost:8001/orderToMemberHystrix,目的就是模拟高并发情况下,是否开启hystrix服务保护功能
4). 通过上图可以看出,当高并发情况下,服务开启了服务降级。查看控制台是否开启服务隔离机制
从图中可以得出,@HystrixCommand默认开启服务降级、隔离、熔断机制
另外,从控制台打印的结果得出,tomcat线程池默认每次执行请求的条数为10条请求
5). 这种方法总结
如上图所示,在使用这种方式整合hystrix服务保护框架时,他的服务隔离机制是同步线程池,即整个方法体都是一个线程池执行处理,真正开发中需要做到只有在调用getMember这个借口时才更换另外的线程池去处理,做到异步线程池隔离,显然这种方法不能做到,效率自然也没有那么高。
6). 这种方法优化
类上添加@DefaultProperties注解指定默认的熔断处理方法后,只需要在方法上添加@HystrixCommand注解即可:
方法二:定义统一的fallback接口—-类的方式
1 定义一个类实现feign客户端调用接口并将其注入到Spring容器中
@Component
public class ImemberFallBackService implements MemberFeign {
@Override
public String getMember(@RequestParam("name") String name){
System.out.println("ImemberFallBackService---线程池名称:"+Thread.currentThread().getName()+"name"+name);
return "采用类的方式做服务降级---服务器忙请稍后重试";
}
}
2 修改其注入feign接口的注解为@Resource,因为使用@Autowired时,当接口存在歧义时容易出错
@Resource
private MemberFeign memberFeign;
3 调用该接口
@Override
@RequestMapping("/orderToMember")
public String orderToMember(@RequestParam("name") String name) {
System.out.println("orderToMember---线程池名称:"+Thread.currentThread().getName()+" name "+name);
return memberFeign.getMember(name);
}
4 总结
采用类方法时,当高并发的情况下,隔离机制采用的是信号量隔离,当请求数超过默认的阈值时(默认是10),
会有请求进入到队列中进行等待(默认为5),但排队队列也有上限,默认是 5,如果排队队列也满,则必定有
请求线程会走fallback流程,从而达到限流和防止雪崩的目的。
当请求超时时,会采用线程池隔离机制(采用的是异步线程),具体证明如下
控制台打印结果如下
这里演示的是当请求超时时,hystrix采用的隔离、降级机制,在配置文件中设置了请求超时时间为5秒,但是在
会员服务的接口中我设置了休眠时间为6秒,这导致了在订单服务端调用时,请求超时,从而证明了在不同情况下
hystrix采用的隔离机制是什么,这种方式采用的异步线程池隔离机制比较符合实际开发。
@RestController
public class IMemberServiceImpl implements IMemberService {
@Override
@RequestMapping("/getMember")
public String getMember(@RequestParam("name") String name) {
System.out.println("跳转到会员服务");
IUser user = new IUser();
user.setName(name);
user.setAge(23);
try {
Thread.sleep(6000);
}catch (Exception e){
}
System.out.println("IMemberServiceImpl :线程池名称 "+Thread.currentThread().getName());
System.out.println("跳转到会员服务:"+user.toString()+"---------------------------------*******************************************");
return user.toString();
}
}
删掉上面代码中try-catch部分,即去掉相应超时,再次测试
- 上一篇: 微服务架构的介绍
- 下一篇: 数据库易忘知识点总结