微服务中,A调用B,B调C,C调D等等,然后最后那个调用服务出问题了,那么请求都会堆积到订单服务,然后最后可能会导致整个项目不可用,这被称为服务雪崩,那我们怎么解决呢?
Spring Cloud最新面试题
Spring Cloud Nacos详解之注册中心
Spring Cloud Nacos详解之配置中心
Spring Cloud Nacos详解之集群配置
Spring Cloud Eureka详解
Spring Cloud Frign详解
Spring Cloud Ribbon详解
Spring Cloud Gateway详解
1.服务提供者不可用: 硬件故障、程序bug、缓存击穿、并发请求量过大等。
2.服务消费者不可用: 同步请求阻塞造成的资源耗尽等。
3.重试加大流量: 用户重试、代码重试等。
服务降级: 当出现请求超时、资源不足时(线程或者信号量),会进行服务降级,就是去返回fallback方法的结果。
服务熔断: 当失败率(网络故障或者超时造成)达到阈值自动触发降级,是一种特殊的降级。
区别: 降级每个请求都会发送过去,而熔断不一定,达到失败率,请求就不会再去发送了。请求出错时熔断返回的是fallback数据,而熔断则是一段时间不会去访问服务提供者。
比如: ①降级:A调B,发送10个请求,即使每个请求都超时,也会去请求B。
②熔断:A调B,发送10个请求,失败率设置为50%,如果5个请求失败,此时失败率到了50%,那么后面的5个请求就不会走到B。
1.服务降级
2.服务熔断
3.服务隔离: 分为线程池隔离和信号量隔离。通过判断线程池和信号量是否已满,超出的请求进行服务降级,从而达到限流的作用。
4.请求合并: 把一段时间内的请求合并成一个。
5.请求缓存: A调B,然后再A添加缓存,之后都从缓存中取,不用去访问B了。使用 Spring Cache实现。
注意: 因为都是服务消费者调用服务提供者出现的错误,所以处理雪崩都是在 服务消费者(A调B,A就是消费者) 中配置。
1.pom文件引入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
2.启动类添加注解
@EnableCircuitBreaker
3.实现降级
注意:fallbackMethod接口的参数、返回值一定和调用的方法一样,不然会报错。
@HystrixCommand(
fallbackMethod = "timeoutError", //服务降级走的方法
commandProperties = { //请求此方法超过三秒就会进行服务降级操作
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@GetMapping("{orderId}")
public String queryOrderByUserId(@PathVariable("orderId") Long orderId) throws InterruptedException {
//睡眠4秒。假设远程调用接口不通
TimeUnit.SECONDS.sleep(4000);
return "我是真爱粉";
}
/**
* 服务降级走的方法
**/
public String timeoutError(@PathVariable("orderId") Long orderId) {
return "你是小黑子";
}
1.服务熔断步骤
1.当调用出现错误时,开启一个时间窗 (默认10秒)。
2.在这个时间窗内,统计调用次数是否达到最小请求数?
(1) 没有达到,则重置统计信息,回到第1步(认为服务没有问题)。
(2) 达到了,则统计 失败的请求数 占所有请求数的百分比,是否达到阈值?
①达到阈值,跳闸(不再请求对应服务,熔断开启)。
②没达到阈值,则重置统计信息,回到第1步(认为服务没有问题)。
3.如果跳闸,开启一个活动窗口(默认5秒),每隔5秒,Hystrix会让一个请求通过,去请求出问题的服务,看是否能调通。
(1) 调通,重置断路器,回到第1步(认为服务没有问题)。
(2) 调不通,回到第3步。
2.实现熔断
//5秒之内,请求数达到3,并且失败率为30%,就跳闸。(在你需要调用其他方法的方法上加)
@HystrixCommand(
fallbackMethod = "timeoutError", //服务降级走的方法
threadPoolProperties = {
//调用的接口出现问题,时间窗口设置为5秒
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "5000"),
//最小请求数为3
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3"),
//判断是否达到阈值,即请求的失败率是30
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "30")
//活动窗口为2秒,熔断之后,2秒内不去请求远程服务
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "2000")
}
)
@GetMapping("{orderId}")
public String queryOrderByUserId(@PathVariable("orderId") Long orderId) throws InterruptedException {
//远程调用用户服务
return "我是ikun"
}
/**
* 服务降级走的方法
**/
public String timeoutError(@PathVariable("orderId") Long orderId) {
return "你是小黑子";
}
优点:
(1) 即使自己线程池满了,也不会影响其他服务。
(2) 每个都有独立线程。一定程度上解决了高并发问题。
(3) 由于线程池的线程有限制,所以一定程度上解决了限流问题。
缺点:
(1) 增加CPU的开销,因为不光有tomcat线程池还有Hystrix线程池。
4.实现线程池隔离
注意:不配置线程池隔离,是tomcat管理线程,配置之后是Hystrix管理线程。
@HystrixCommand(
groupKey = "ikun",//在同一组的共用一个线程池(一般用于同一个服务、同一个功能的CRUD等)
commandKey = "queryOrderByUserId", //可以自定义,一般是当前方法名
threadPoolKey = "ikun",//唯一线程池名称,所以默认和 groupKey 名称一样
threadPoolProperties = {
@HystrixProperty(name="coreSize",value="5"), //线程池大小为5
@HystrixProperty(name="maxQueueSize",value="2") //阻塞队列最大为250
}
)
@GetMapping("{orderId}")
public String queryOrderByUserId(@PathVariable("orderId") Long orderId) throws InterruptedException {
// 返回当前线程名称
return Thread.currentThread().getName();
}
1.信号量是什么?
java.util.concurrent.Semaphore用来控制同时并发的线程数。通过构造方法决定线程执行数量,每次执行线程会去acquire() 方法获取许可,然后执行完通过 release() 方法释放许可,如果们没有许可用,那就线程阻塞,等待其他线程释放许可。
信号量隔离技术:设置信号量初始值,信号量相当对关卡,每个线程通过关卡,信号量 -1,直到为 0,不允许线程通过,然后执行fallback(),进行限流。(简单来说就是一个计数器)
2.实现信号量隔离
@HystrixCommand(
commandProperties = {
//开启信号量隔离模式 THREAD:线程池隔离(默认), SEMAPHORE:信号量隔离
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value = "SEMAPHORE"),
//信号量隔离最大并发数为5,超过5个请求会走服务降级方法
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value = "5"),
//服务降级并发量最大为3,超过3个请求会阻塞
@HystrixProperty(name = HystrixPropertiesManager.FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value = "3"),
},
//服务降级方法
fallbackMethod = "timeoutError"
)
@GetMapping("{orderId}")
public String queryOrderByUserId(@PathVariable("orderId") Long orderId) throws InterruptedException {
// 调用用户服务逻辑
return "我是ikun";
}
/**
* 降级方法
**/
public String timeoutError(@PathVariable("orderId") Long orderId) {
return "你是小黑子";
}
1.什么情况需要请求合并
高并发下需要请求合并,因为tomcat并发量大约在600,多出的请求都会进行等待,导致响应延迟。
2.请求合并的缺点
如果我们需要把20ms内的请求合并起来,本来一个请求1ms就执行完,但是这个请求现在还需要等待19ms,才能执行,增加了耗时。但是如果是一个高延迟命令,那么就可以请求合并。
3.请求合并的参数
参数 | 作用 | 注意事项 |
---|---|---|
@HystrixCollapser |
请求合并注解 | @HystrixCollapser 标注的方法,返回值类型必须是 Future (使用异步方法,进行请求合并)。 |
batchMethod | 合并请求的方法 | 方法只能接收一个参数,如果需要多个参数,那就封装成一个类参数。 |
scope | 请求方式(默认REQUEST) | REQUEST:对一个request请求内的多次服务请求进行合并。GLOBAL:多个单个应用中的所有线程的请求中的多次服务请求进行合并。 |
timerDelayInMilliseconds | 合并请求的时间(默认10ms) | 建议尽量时间不要太大 |
maxRequestsInBatch | 最大合并请求数(Integer.MAX.VAULE) |
4.实现请求合并
注意:
@HystrixCollapser(
//请求合并走的方法
batchMethod = "hebing",
//开启GLOBAL模式
scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL
collapserProperties = {
//合并10ms内的请求
@HystrixProperty(name = "timerDelayInMilliseconds",value = "10"),
//最多合并250个请求
@HystrixProperty(name = "maxRequestsInBatch",value = "250"),
}
)
//这个方法因为被请求合并了,所以恒不被执行,他的请求都被 hebing() 这个方法接收执行了
@GetMapping("{orderId}")
public Future<Long> queryOrderByUserId(@PathVariable("orderId") Long orderId) throws InterruptedException {
//远程调用服务
return "ok"
}
/**
* 合并请求的方法
* 为啥返回值List呢,因为多个请求的返回值放在一起了,
* List(0)是第一个请求的返回值,依次类推。所以传的参数也是一个道理。
**/
@HystrixCommand
public List<Long> hebing(@PathVariable("orderId") List<Long> orderIds) {
List list = 调用远程服务(远程服务方法返回值是Long类型)
return list;
}
/**
*10ms调用两次,然后会请求合并,因为你10ms之内,你就算单身50年,你也点不出来第二下
**/
public List<Long> moni(@PathVariable("orderId") List<Long> orderIds) {
//调用两次 queryOrderByUserId() 方法
return "调用成功";
}
注意:只监测和Hystrix相关的接口。
Hystr-dashboard能够让 Actuator(SpringBoot的监控器:返回是json,观看不便) 转为界面视图。
1.pom文件添加依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
2.配置yml文件
management:
endpoint:
web:
exposure:
include: hystrix.stream
3.启动类配置注解
@EnableHystrixDashboard
4.启动项目 http://ip+端口/hystrix
解决: 启动类里编写(springboot2.0以上才需要编写)
//springboot2.0以上版本,使用hystrix的dashboard要配置一个servlet
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
重新尝试访问监视路径:http://ip+端口/actuator/hystrix.stream
或 http://ip+端口/hystrix.stream
页面如下就对了。
在yml文件添加,重新访问 http://ip+端口/hystrix
//添加允许访问列表
hystrix:
dashboard:
proxy-stream-allow-list: "*"