Sentinel 微服务保护
关于服务雪崩的问题
服务雪崩指的是微服务调用链路中的某个服务发生故障,所引起的上层服务全部出现错误的情况。
如下图所示:
当服务A可能调用到服务D,而服务D却发生故障时,服务A的依然不断地向服务D发送请求,但服务D并没有响应,此时的服务A会不停地累积请求,直致服务A的资源耗尽,这时即使希望想访问服务B也无法访问了。
而服务A只是整个服务中的其中一个下游服务节点,会因服务D的故障,使得所有上游服务全部发生故障,这就是服务雪崩。
解决服务雪崩
通常我们有4种方案解决服务雪崩的问题:
1.超时处理
- 设定请求的超时时间,当响应时间低于设定时间后,服务A会认为服务D已经出现故障,从而释放资源。
- 但是超时处理在高并发上作用不明显,只能缓解资源的耗尽速度,因为一旦请求的并发数量高于超时速度时,雪崩也只是时间的问题
2.舱壁模式
- 限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,也叫线程隔离,如下图中的概念
- 就像轮船上的船壁,即使船底发生破裂,也只会影响部分船底,而不至于整个船底都发生渗水。
- 在服务中我们可以事先设置好每一个服务请求的最多线程数,这样服务C发生故障时,也不会影响服务B的使用,但缺点是,即使知道服务C已故障,但依然分配线程给C进行不断访问,这是一种资源的浪费。
3.熔断降级
- 由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
- 我们可以设定一个异常比例,若服务的请求失败次数超过一定比例后,直接对该服务进行熔断,所有前来访问的所有请求直接拒绝。
4.流量控制(预防性措施)
- 当服务仅能处理一定量的请求时,我们可以利用Sentinel控制请求的流量,使得服务不会在瞬间超出了可处理的请求能力范围值,预防服务在出现瞬间服务过多所引起的服务崩溃问题。
Sentinel 安装与引入
Sentinel 安装
可以到以下地址下载最新版本的Sentinel 进行安装
https://sentinelguard.io/zh-cn/index.html
它是一个SpringBoot的工程,可以通过 java -jar Sentinel.jar
命令运行Sentinel
用户名和密码默认为 sentinel 可以使用自定义配置对这些进行配置,参考以下网址:
https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
引入 Sentinel
Sentinel 运行后,默认后台是什么都没有的,这是因为我们还没有把Sentinel引入到项目中,这样Sentinel就无法对我们项目中的请求做限流操作,所以我们需要再引入Sentinel的包,让它监控我们的项目。
本地下载地址:
https://www.tzming.com/wp-content/uploads/filedown/sentinel-dashboard-1.8.6.jar
1.引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.配置参数
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
3.Sentinel 会自动对每一个请求方法(端点)做监听,当有请求时,Sentinel就会被触发
Sentinel 流控使用
流控规则
我们认为,每一个请求url都是一个资源或端点,我们点开一个资源的【流控】按钮
QPS 是控制在相同时间内的异步请求数量,Sentinel支持QPS限制和线程数限制。
单机阈值指的是限制的数量,若请求数超出这个值,则多出来的请求会被拒绝,阈值应当在压测之后得出实服务器实际能承受的上限值作为阈值。
流控模式-关联
流控模式-关联是指当关联资源的请求发生超过阈值时,被设置的资源将被拒绝,如上图中的 /read 请求设置了关联资源 /write
当 /write 比较繁忙时, /read 就会被拒绝请求,这种需要多出现在数据库的写与读的关系,当数据库的写相对繁忙时,应该优先给写资源,以免读在正在写中的行,而影响数据的真实性
流控模式-链路
流控模式-链路 是指,对请求的路径进行筛选,当某一个service中的业务被多个请求所调用时,我只希望对某个请求调用的service业务进行限制,其它的调用不限制,如下图
图中的 /common 是被调用的service业务
入口资源则是 调用 service 业务的请求
设置后,只有通过请求 /test2 方法所调用的 /common 才会被限制。
链路生效步骤
链路是会被包含到方法中调用的外部方法,比如:
controller 请求方法 a() 会调用 service 业务方法 b()
1.但是链路模式不单止会监控 a() 而且还需要监控 b() ,所以 b() 方法中必须加入注解【@SentinelResource】来标记该方法是需要给Sentinel监控的资源。
2.Sentinel 默认会把 ServletContext 作为请求的根路径,其它controller 方法都是 ServletContext 下的子路径,我们可以看回上面章节的图
忽略红色框,可以看到,所有请求节点中,根链路是【sentinel_spring_web_context】,这使得我们的请求url变成了子链路,所以我们需要做一个配置,关闭统一根链路,而是由我们的请求方法作为根链路,这样根链路下调用的 service 业务方法才得以监控得到:
spring:
cloud:
sentinel:
web-context-unify: false # 关闭context整合
Sentinel 流控效果
快速失败
快速失败是一个比较粗暴的效果,即当QPS达到了峰值后,被拒绝的请求直接报出请求失败的异常。无任何缓解措施
warm up
预热模式,我们可以设定最大的QPS请求峰值,最小的QPS由Sentinel决定,默认为3。预热时间为5秒
模拟效果是:
1.当某一瞬间发来10个请求时,QPS在3处,此时10个请求,只有3个能通过,7个会直接拒绝
2.当下一瞬间依然发来10个请求时,QPS开始从3个慢慢增加到10个,用时为5秒,当QPS增加到5时,10个请求中5个能通过,5个直接拒绝
3.当5秒过后,QPS已从3增加到10个,此时发来10个请求时,10个请求都能通过。
排队等待
当请求数超过设定的QPS峰值时,不会马上拒绝请求,而是这些请求进行排队执行,同时设置超时时间5秒。
模拟效果是:
1.当某一瞬间发来10个请求时,若设置QPS为5时,那么这一瞬间,有5个请求被处理,有5个请求等待中
2.若上一瞬间的5个请求还没处理时,下一瞬间又发来10个请求,此时,上一瞬间的5个等待请求被执行,当前瞬间的10个请求等待中。
3.若如此以往,等待的请求会越来越多,后面发来的请求可能会出现等待时间越来越长的情况,若后来的请求等待时间超过5秒(用户设置的),这些等待的请求直接拒绝。
热点参数限流
热点参数限流是一种更细粒度的限流方式,是指分别统计参数值相同的请求,判断是否超过QPS阈值。
比如某请求 /goods/{id},当出现多个请求不同参数时
/goods/1
/goods/1
/goods/1
/goods/2
那么 id=1 的有3个,id=2 的有1个。
我们就可以分别能过限流 id=1 的QPS和 id=2 的QPS阈值了。
我们可以在【热点限流】中添加对请求的热点限流:
参数索引:指的是请求方法中的接收参数的位置,从0开始
单机阈值:默认的请求阈值,即如果不另外设置的话,就使用这个阈值
让请求生效热点规则
要让请求方法生效热点规则,需要在请求方法前加上注册【@SentinelResource("")】
@SentinelResource("hot") // 传入设定好的热点限流规则名称
@GetMapping("/{id}")
public String test1(@PathVariable("id") Long id){
return "hhhh";
}
隔离和降级
FeignClient整合Sentinel
在微服务中,我们的服务与服务之间的通信都是使用Fiegn进行的,那么Fiegn是否请求成功,我们是无法知道的,因此我们可以把Fiegn整合到Sentinel中,让Sentinel发现Figen的操作。
实现步骤:
1.Fiegn中配置开启Sentinel
feign:
sentinel:
enabled: true # 开启Feign的Sentinel功能
2.给FiegnClient中的代码加入请求失败后的降级逻辑
在SpringCloud的基本章节中,我们使用了Fiegn利用接口来实现请求方法,那么如果这些请求出现错误时,我们可以使用以下两种方法,对请求失败后的兜底操作,如把通用的信息返回给用户不至于直接报错。
- 方式一:FallbackClass,这种方法无法对远程调用的异常做处理
- 方式二:FallbackFactory,可以对远程调用的异常做处理,推荐使用。
我们通过编写一个 FallbackFactory 接口的实现,来对Fiegn请求失败时的执行方法:
原有的UserClient请求接口:
@FeignClient(value = "userservice",fallbackFactory = UserFallBackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
String getUserInfo(@PathVariable Long id);
}
用于兜底UserClient报错的方法:
public class UserFallBackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public String getUserInfo(Long id) {
return "查询用户失败,这里是备用请求方案";
}
};
}
}
然后再在Config中注册这个类为一个Bean即可。
@Configuration
public class FiegnConfig {
@Bean
public UserFallBackFactory userFallBackFactory(){
return new UserFallBackFactory();
}
}
然后在FiegnClient接口中的@FeignClient注解上加上 fallbackFactory 的值,值为这个兜底类的字节码文件,如上面步骤1中的代码。
线程池隔离(仓壁模式)
从上面节章我们知道,线程池模式就像船底的仓壁,每个服务都分配访问线程数,即使某些服务出现问题,也不会影响其它服务的访问
而FeignClient中支持使用线程池隔离的方式
线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现舱壁模式。
熔断降级
熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。既拦截访问该服务的一切请求,而当服务恢复时,断路器会放行访问该服务的请求。
断路器包含三种状态,分别为 关闭断路器:Closed、打开断路器:Open、半打开状态:Half-Open
关闭断路器:Closed -> 断路器关闭,任何请求都会被处理。
打开断路器:Open -> 断路器开启,任何请求都会被直接拒绝。
半打开状态:Half-Open -> 断路器开启时长到期,进行半开状态检测服务是否请求成功,如果成功,则改为Closed,如果依然请求失败,则改为Open
我们可以在簇点链路列表中的熔断按钮进行设置
熔断策略:慢调用
慢调用指的是判断服务调用时所耗的时长,比如有一些服务可能出现了问题,调用所需耗时比较大,这种称为慢调用
最大RT : 处理整个请求所需的耗时阈值,如果超出这个阈值则会触发熔断规则。
比例阈值 : 在多个请求中,超出最大RT的请求占总请求数的比例,如果超过了,则会触发熔断规则。
熔断时长 : 熔断并非一直开启,而是暂时开启,所设定的时长即是熔断后开启的时长。
统计时长与最小请求数 : 当熔断进入半开状态时,断路器会在1000毫秒内开放最少5个请求数,如果统计后的数据依然符合熔断规则,则断路器继续开启熔断,否则关闭熔断机制。
熔断策略:异常比例
异常比例是指,以抛出异常为统计点的熔断规则,当请求中发生异常抛出的数量在总体请求中的比例达到设定阈值时,断路器开启熔断。
熔断策略:异常数
异常数是指设定具体数量的异常抛出值,当请求中抛出的异常超过了预设定好的异常数后,断路器开启熔断。
授权规则
授权规则
授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式
白名单
白名单即仅允许指定 origin 来源的请求
资源名 : 被保护的请求,只有允许的来源才可能访问。
流控应用 : 流控应用不是服务名,而是由我们自定义的Origin
1.通过实现接口 RequestOriginParser 来定义来源:
@Component
public class OriginParser implements RequestOriginParser {
/**
* 在这里返回一个 Origin 名称
* @param httpServletRequest
* @return
*/
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 自定义设置一个origin规则,比如我自己定义header中带有origin的才允许
String origin = httpServletRequest.getHeader("origin");
// 如果请求中不带有 origin 的值,则不允许访问
if (StringUtils.isEmpty(origin)){
return "blank";
}
// 假如header中的origin=gatway,那么就只授权origin为getway的规则
return origin;
}
}
其中 返回的 origin 的值则是上面的流控应用的名称
当我们使用的是统一网关的转发请求的话,我们就可以使用网关的过滤器功能,往header中加入origin的头信息再转发到服务中请求了,关于网关可查看微服务基础。
黑名单
黑名单即除指定 origin 来源以外的请求都允许
自定义异常结果
我们以上章节中,所有的设定限流后,常发生拒绝后,都是直接返回一串文字
Blocked by Sentinel (flow limiting)
相对来说不太友好
我们可以通过自定义异常的结果返回,通过实现接口 BlockExceptionHandler 的方法来实现自定义异常的结果
@Component
public class ExceptionResult implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
}
}
当处理请求被限流、降级、授权拦截时抛出的异常:BlockException
我们可以通过 BlockException 来判断是什么类型的异常,通下异常都为 BlockException的子类
异常 | 说明 |
FlowException | 限流异常 |
ParamFlowException | 热点参数限流的异常 |
DegradeException | 降级异常 |
AuthorityException | 授权规则异常 |
SystemBlockException | 系统规则异常 |
举个例子:
@Component
public class ExceptionResult implements BlockExceptionHandler {
@Override
public void handle(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;
if (e instanceof FlowException) {
msg = "请求被限流了!";
} else if (e instanceof DegradeException) {
msg = "请求被降级了!";
} else if (e instanceof ParamFlowException) {
msg = "热点参数限流!";
} else if (e instanceof AuthorityException) {
msg = "请求没有权限!";
status = 401;
}
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.setStatus(status);
httpServletResponse.getWriter().println("{\"message\": \"" + msg + "\", \"status\": " + status + "}");
}
}
规则持久化
规则管理模式
Sentinel的控制台规则管理有三种模式
原始模式:默认模式,将规则保存在内存中,重启服务后就会丢失
Pull 模式 : 控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
Push 模式 : 控制台将配置规则推送到远程配置中心,例如Nacos. Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
Nacos 中拉取配置
Sentinel 中的配置可以通过预先在 Nacos 中配置好配置项,然后当带有Sentinel服务启动后,再向Nacos中拉取限流规则,并以yaml配置形式配置到Sentinel中。缺点是Sentinel UI界面上修改的规则不能同步到Nacos中,同时也属于临时规则,只要Sentinel服务重启了,规则也自然没了。
步骤:
1.在微服务中引入依赖坐标(数据源坐标)
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.在微服务中配置Sentinel数据源,以访问Nacos拉取规则
cloud:
sentinel:
enabled: true
transport:
dashboard: localhost:8090
http-method-specify: true # 开启请求方式前缀显示,否则Sentinel中不能区分是post还是get
datasource:
ds: # 自定义的数据源名称(唯一名称)
nacos: # 定义是从Nacos获取
server-addr: 192.168.101.150:8848 # Nacos 地址
groupId: DEFAULT_GROUP
data-type: json
data-id: degrade.json # Nacos中定义规则的文件 Id
rule-type: degrade # 定义这个规则用于 Sentinel 限流类型(如用于熔断,还是限流,还是降级等其它功能)
3.在Nacos中创建一个配置文件,如上面配置的文件Id为 degrade.json,则我们在Nacos中定义的文件Id也是 degrade.json
对于Sentinel 而言,慢调用降级限流 degrade 包含以下几个规则数据
- 最大RT: 调用API时响应的时间大于最大RT,则认为该API出现异常
- 熔断时长:对于被熔断降级的API,需要隔一段时间去尝试调用,看看是否恢复了,这个时间间隔则是熔断时长
- 比例阈值:值在0.0~1.0之间,当阈值为0.5时,假如有10个调用中有5个以上出现超过最大RT值,则认为应该熔断处理
- 最小请求数:即放出多少个测试用的请求来判断API是否恢复,即上面说的10个调用
- 统计时长:在设定的时间内,吸收判断用的请求数。
在代码中表示degrade数值的是 DegradeRule 对象,在Nacos中配置规则时,应当按照以下方式进行配置
- grade:熔断类型(慢调用,异常比例,异常数)
- count:慢调用是,它表示 最大RT,在异常比例下,它表示异常数量占最小请求数比,在异常数下,它表示异常数据量
- timeWindow:表示熔断时长
- slowRatioThreshold:表示比例阈值
- minRequestAmount:表示最小请求数,默认为5
- statIntervalMs: 表示统计时长
最后在Nacos中配置的json规则如下:
[
{
"resource": "GET:http://xxx-service/xx",
"count" : 200.0,
"grade": 0,
"slowRatioThreshold": 0.5,
"timeWindow": 10
}
]
至此,当我们的Sentinel服务启动后,就会自动向Nacos拉取规则了。
有关其它规则的配置信息,可以查看 com.alibaba.cloud.sentinel.datasource.config.DataSourcePropertiesConfiguration 类,里面就是包含了所有动态配置规则的规则,datasource 配置 只是一个 Map<String, DataSourcePropertiesConfiguration> 的变量保存规则。
共有 0 条评论