1.雪崩
微服务链路上某个服务出现了问题,结果导致整个微服务调用链上所有服务都出现了问题,这就是雪崩。
2.解决雪崩问题的常见方式有四种
1.超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
2.舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat资源,因此也叫线程隔离
3.熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
4.流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。QPS:每秒钟处理的请求的数量
注意:
1.流量控制可以避免因瞬间高并发流量而导致服务故障。
2.超时处理,线程隔离,熔断降级避免因服务故障引起的雪崩问题
3.超时处理只是缓解了雪崩问题。(比如在1个请求的超时时间过程中又来了其他请求,随着时间推移,还是会耗尽服务的资源)
4.线程隔离由于会创建很多的线程,会造成一定的资源浪费。
5.只有流量控制是预防雪崩,其他三种都是防止已有服务故障,然后又传递到其他的服务中去。
3.线程保护技术
注意:
1.线程池隔离:
一个业务请求进入tomcat中的时候,给每一个被隔离的业务创建独立的线程池,所以比直接的方式会多出很多很多线程,虽然隔离性好,但是由于线程的数量增多,会造成更多的CPU上下文切换的消耗,所以整个服务性能会下降。
2.信号量隔离:
当业务请求进入tomcat后,不会创建独立线程池,而是统计当前业务已经使用了几个线程了,限定线程的数量,当业务请求数量超过了这个量就会拒绝,限制每个业务能使用的线程数量,减少了线程的创建,在隔离的基础上并没有影响性能。
4.Sentinel
阿里巴巴开源的一款微服务流量控制组件: https://sentinelguard.io/zh-cn/index.html
在GitHub下载,使用时直接启动jar包即可: java -jar sentinel-dashboard-1.8.1.jar
然后账号密码都默认为 sentinel
1.微服务整合Sentinel
1.引入sentinel依赖
<!--sentinel,整合Sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.配置控制台地址
spring:
cloud:
sentinel:
transport:
#配置控制台地址
dashboard: localhost:8080
3.访问微服务的任意端口,触发sentinel监控
5.限流规则
1.簇点链路
就是 项目内的调用链路(请求->springmvc->controller->service->mapper->),链路中被监控的每个接口就是一个资源。 默认情况下sentinel会监控SpringMvc的每一个端点(Endpoint)(可以理解成controller中的方法),因此SpringMVC的每一个端点就是调用链路中的一个资源。流控,熔断等都是针对簇点链路中的资源来设置的。
2.流控模式
在添加限流时,点击高级选项,可以选择三种流控模式:
直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式。
关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流。
链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流。
1.关联模式
关联模式使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是优先支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。
注意:满足什么条件可以使用关联模式:
1.两个有竞争关系的资源。
2.一个优先级较高,一个优先级较低。
2.链路模式
只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
场景:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查询商品的请求统计,并设置限流。(使其不影响创建订单时的性能)
注意:
1.Sentinel默认只标记Controller中的方法为资源,如果要标记其他方法(service层),需要利用@SentinelResource注解
@SentinelResource("goods") //标记一个资源并起一个名字:goods
public void queryGoods(){
System.err.println("查询商品");
}
2.Sentinel默认会将Controller方法做context整合,导致链路模式的流控失效,需要修改application.yml,添加配置
spring:
cloud:
sentinel:
#关闭context整合
web-context-unify: false
总结:
直接模式:对当前资源限流
关联模式:高优先级资源触发阈值,对低优先级资源限流
链路模式:阈值统计时,只统计从指定资源进入当前资源的请求,是对请求来源的限流
3.流控效果
快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长
1.warm up
是应对服务冷启动(为了避免冷启动那一刻过高并发导致服务故障)的一种方案。请求阈值初始值是(最大阈值)threshold/(冷启动因子,默认是3)coldFactor,持续指定时长后,逐渐提高到threshold值。而coldFactor的默认值是3.
2.排队等待
当请求超过QPS阈值时,快速失败和warm up会拒绝新的请求并抛出异常。而排队等待则是让所有请求进入一个队列中,然后按阈值允许的时间间隔依次执行。后来的请求必须等待前面的执行完成,如果请求预期的等待时间超过最大时长,则会被拒绝。(流量整形)
例子:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待超过2000ms的请求会被拒绝并抛出异常
总结:
快速失败:QPS超过阈值时,拒绝新的请求
warm up: QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提升的,可以避免冷启动时高并发导致服务宕机
排队等待:请求会进入队列,按照阈值允许的时间间隔依次执行请求;如果请求预期等待时长超过超时时间,直接拒绝。
4.热点参数限流
之前的限流是统计 访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是 分别统计参数值相同的请求,判断是否超过QPS阈值。
注意:
热点参数限流对默认的SpringMVC资源无效。只有用@SentinelResource声明的资源才可以配置热点参数限流
@SentinelResource("hot")
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
6.隔离和降级
1.FeignClient整合Sentinel
虽然 限流可以尽量避免 服务器因过高并发而引起服务故障,但服务还会 因为其他原因而故障。而要将这些故障 控制在一定的范围内,避免雪崩,就要靠 线程隔离和 熔断降级手段了。(不管是线程隔离还是熔断降级,都是对客户端(调用方)的保护。
SpringCloud中, 微服务调用都是通过Feign来实现的,因此做客户端保护必须整合Feign和Sentinel
1.修改application.yml文件,开启Feign的Sentinel功能
feign:
#开启Feign对Sentinel的支持
sentinel:
enabled: true
2.给FeignClient编写失败后的降级逻辑
1.FallbackClass,无法对远程调用的异常做处理。
2.FallbackFactory,可以对远程调用的异常做处理。(用这个)
步骤一:实现FallbackFactory
/**
* 实现FallbackFactory
*/
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
//创建UserClient接口实现类,实现其中的方法,编写失败降级的处理逻辑
return new UserClient() {
@Override
public User findById(Long id) {
// 记录异常信息
log.error("查询用户失败",throwable);
//根据业务需要返回默认的数据,这里是空用户
return new User();
}
};
}
}
步骤2:将UserClientFallbackFactory类在配置类中注册成一个Bean
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
步骤3:在UserClient接口中使用UserClientFallbackFactory
@FeignClient(value = "userservice",fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
总结:
1.在application.yml中配置:feign.sentienl.enable=true
2.给FeignClient编写FallbackFactory并注册为Bean
3.将FallbackFactory配置到FeignClient
2.线程隔离(舱壁模式)
线程池隔离:
一个请求过来,会给它依赖的每个服务都创建一个线程池,不会使用请求自己的线程,而是让请求在每个线程池中都取出一个线程,然后用这些线程去调用feign的客户端,这样,服务就隔离了,如果有个服务出现了故障,就是线程池满了,然后就不会再接受新的请求,所以不会把调用方的资源给耗尽了,所以就把故障给隔离了。
信号量隔离(Sentinel默认采用):( 网关基本采用信号量模式)
不会去创建新的线程,而是用请求自己的线程去调用feign的客户端,但是会在请求进入时做一个判断,计数器记录一个信号量,每有一个请求进入,计数器就减一,当计数器为0了再有新的请求进入就会被拒绝。当请求处理完,信号还要还回去。
优点 | 缺点 | 场景 | |
信号量隔离 | 轻量级,无额外开销 | 不支持主动超时 不支持异步调用 | 高频调用 高扇出 |
线程池隔离 | 支持主动超时 支持异步调用 | 线程的额外开销比较大 | 低扇出 |
QPS:就是每秒的请求数
线程数:是该资源能使用的tomcat线程数的最大值,也就是通过限制线程数量,实现舱壁模式
总结:
1.线程隔离的两种手段:
信号量隔离
线程池隔离
2.信号量隔离特点:
基于计数器模式,简单,开销小
3.线程池隔离特点:
基于线程池模式,有额外开销,但隔离控制更强
3.熔断降级
熔断降级是解决雪崩问题的重要手段,其思路是由 断路器统计服务调用的异常比例,
慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复
时。断路器会放行访问该服务的请求。
断路器通过内部的状态机来实现熔断和放行,当断路器的状态为 Closed的时候,断路器
不会拦截任何请求,但是断路器会去 统计异常的比例,如果发现异常的比例过高达到了阈值就会
切换状况到 Open, 拦截进入该服务的一些请求这就是熔断,熔断会持续一段时间,当熔断时间结束
就会切换状态到 Half-Open这时会 尝试放行一次请求,根据这次请求的结果来决定接下来的行为,
如果请求失败会再次进入OPen状态拦截请求进入熔断,一直循环直到请求成功,切换状态到Closed,
这时就可以放行该服务的请求了。
达成熔断的条件就是 熔断策略。
失败阈值和熔断时间由我们配置。
熔断策略 | 详细说明 |
慢调用 | 业务的响应时长大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。 |
异常比例 | 异常比例或异常数:统计指定时间内的调用,如果调用次数超过请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。 |
异常数 | 比较的是异常数量。 |
总结:
Sentinel熔断降级的策略:
慢调用比例:超过指定时长的调用为慢调用,统计单位时长内慢调用的比例,超过阈值则熔断
异常比例:统计单位时长内异常调用的比例,超过阈值则熔断
异常数:统计单位时长内异常调用的次数,超过则熔断
7.授权规则
1.授权规则
授权规则可以对调用方的来源做控制,有 白名单和 黑名单两种方式
白名单:来源在白名单内的调用者允许访问
黑名单:来源在黑名单内的调用者不允许访问
假如我们只允许从网关进来的请求可以访问,那么流控应用中就应该写网关的名称
注意:Sentinel是通过RequestOriginParser(请求来源解析器)这个接口的parseOrigin来获取请求的来源的。但是这个接口的访问结果都是default,所以我们需要想办法自己实现这个接口来实现从网关进入的请求和浏览器进入的请求返回不同的结果。
实现方法:给网关过来的请求带上额外的请求头,即可以区分网关和浏览器的请求。利用网关的过滤器添加名为origin的getway头。(约定好即可)
1.实现RequestOriginParser接口方法
/**
* 请求来源解析器
*/
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
//1.获得请求头
String origin = request.getHeader("origin");
//2.非空判断
if (StringUtils.isEmpty(origin)){
origin="blank";//blank的是浏览器传入的请求
}
return origin;//不为blank的是网关传入的请求
}
}
2.在网关中,利用网关的过滤器添加名为gateway的origin头
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,gateway #添加一个名为origin的请求头,值为gateway
2.自定义异常结果
默认情况下,发生限流,降级,授权拦截时,都会抛出异常到调用方。如果要自定义异常时
的返回结果,需要实现 BlockExceptionHandler接口。
(阻塞异常处理器)
/**
* 阻塞异常处理器
*/
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg="未知异常";
int status=429;
if (e instanceof FlowException){
msg="请求被限流了";
} else if(e instanceof ParamFlowException){
msg="请求被热点参数限流了";
}else if(e instanceof DegradeException){
msg="请求被降级了";
}else if(e instanceof AuthorityException){
msg="没有权限访问";
status=401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\":"+msg+",\"status\":"+status+"}");
}
}
总结:
获取请求来源的接口:
RequestOriginParser
处理BlockException的接口:
BlockExceptionHandler
8.规则持久化
1.规则管理模式
推送模式 | 说明 | 优点 | 缺点 |
原始模式 | API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource),默认就是这种 | 简单,无任何依赖 | 不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境 |
Pull模式 | 扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等 | 简单,无任何依赖,规则持久化 | 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。 |
Push模式 | 扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。 | 规则持久化,一致性 | 引入第三方依赖 |
原始模式:控制台配置的规则直接推送到Sentinel客户端,也就是我们的应用。然后保存在内存中,服务重启则丢失
pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库查询,更新本地规则。
push模式:控制台将配置规则推送到远程配置中心,例如Nacos,Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
总结:
原始模式:保存到内存中
pull模式:保存在本地文件或数据库,定时去读取
push模式:保存在nacos,监听变更实时更新
2.实现push模式
1.引入依赖,引入sentinel监听nacos依赖
<!--引入sentinel监听nacos依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.配置nacos地址,在yml文件中配置nacos地址以及监听的配置信息
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: localhost:8848 # nacos地址
dataId: orderservice-flow-rules #配置文件名称
groupId: SENTINEL_GROUP #配置组
# 限流规则
rule-type: flow # 还可以是:degrade降级,authority授权,param-flow热点参数限流
# degrade:
# nacos:
# server-addr: localhost:8848 # nacos地址
# dataId: orderservice-degrade-rules #配置文件名称
# groupId: SENTINEL_GROUP #配置组
# # 限流规则
# rule-type: degrade # 还可以是:degrade降级,authority授权,param-flow热点参数限流
3.修改sentinel-dashboard源码(默认不支持nacos的持久化)
4.重新编译,打包