文章目录
SpringCloudAlibaba-Sentinel服务容错
在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。(常见的如高峰期网页提示:当前人数太多,请稍后再试!)
Sentinel官方文档:https://sentinelguard.io/zh-cn/index.html
一、Sentinel的概念和功能
基本概念
-
资源
资源就是Sentinel要保护的东西,资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,可以是一个服务,也可以是一个方法,甚至可以是一段代码。
-
规则
规则就是用来定义如何进行保护资源的,作用在资源之上, 定义以什么样的方式保护资源,主要包括流量控制规则、熔断降级规则以及系统保护规则。
-
重要功能
Sentinel的主要功能就是容错,主要体现为下面这三个:
-
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
-
熔断降级
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
Sentinel 对这个问题采取了两种手段:
1 通过并发线程数进行限制
Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
2 通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
Sentinel 和 Hystrix 的区别:
两者的原则是一致的, 都是当一个资源出现问题时, 让其快速失败, 不要波及到其它服务,但是在限制的手段上, 确采取了完全不一样的方法:
Hystrix 采用的是线程池隔离的方式,优点是做到了资源之间的隔离, 缺点是增加了线程切换的成本。
Sentinel 采用的是通过并发线程的数量和响应时间来对资源做限制。-
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。当系统负载较高的时候,如果还持续让请求进入可能会导致系统崩溃,无法响应。在集群环境下,会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
-
二、名词解释
- 服务限流:好似去景点玩,景点限流。
目的为了保护我们的服务,在高并发情况下,如果客户端的请求服务器端达到一定的极限(设置阈值),请求的数量超出我们设置的阈值,开启我们的自我保护机制,直接执行我们的服务降级的方法,不会去执行我们的业务逻辑,走本地的 fallback 方法。
- 服务降级:类比秒杀一件商品,当前排队人数过多,请稍后重试。
在高并发的情况下,为了防止用户一直等待,采用限流和熔断机制,保护我们的服务,不会执行我们的业务逻辑,走本地 fallback 方法,返回一个友好的提示给客户端。
- 服务雪崩的效应:
在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 100% 可用。如果一个服务出现了问题,调用这个服务就会出现线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等待,进而导致服务瘫痪。
由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 雪崩效应。
- 服务雪崩解决方案:
服务的隔离机制:线程池隔离 或者 信号量隔离机制
线程池隔离:每个接口都由自己的独立的线程池维护我们的请求、每个线程池互不影响。缺点:占用服务器内存非常大。
信号量隔离:设置最多允许我们某个接口有一定的阈值线程数量去处理接口,如果超出线程数量,拒绝访问。
三、Sentinel 和 Hystrix 的区别(直接上alibaba官方图)
四、配置限流的方式
本人另一篇笔记,sentinel五大规则详细讲解(建议先看完本篇再衔接看另一篇):
https://blog.csdn.net/weixin_44780078/article/details/128242453?spm=1001.2014.3001.5501
-
手动代码配置(纯代码、主机的形式)
-
纯手工方式整合(不推荐,只演示demo)
sentinel依赖
<!-- sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2021.1</version> </dependency>
mumberService.java
@RestController public class MumberService { private static final String GETUSER_KEY = "getUser"; @GetMapping("/getUser") public String getUser(){ Entry entry = null; try { entry = SphU.entry(GETUSER_KEY); //正常执行逻辑 return "getUser接口..."; } catch (BlockException e) { //接口被限流的情况下,走catch return "当前访问人数太多,请稍后重试!"; } finally { if (entry != null){ entry.exit(); } } } /** * 初始化路由策略 */ @RequestMapping("/initFlowQpsRule") public String initFlowQpsRule(){ List<FlowRule> ruleList = new ArrayList<>(); FlowRule rule = new FlowRule(); //QPS控制在1以内 rule.setCount(1); //QPS限流 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); ruleList.add(rule); FlowRuleManager.loadRules(ruleList); return "初始化策略成功..."; } }
接下来先初始化,即访问initFlowQpsRule接口,然后再访问getUser接口,当访问getUser接口的频率快于1s时,就会自动限流。
若想实现自动装配(不访问initFlowQpsRule接口),则实现ApplicationRunner类:
SentinelApplicationRunner.java
public class SentinelApplicationRunner implements ApplicationRunner { private static final String GETUSER_KEY = "/getUser"; //一般是请求路径或有SentinelResource注解类定义的访问资源 @Override public void run(ApplicationArguments args) throws Exception { initFlowQpsRule(); } public String initFlowQpsRule(){ List<FlowRule> ruleList = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setCount(0.5); //QPS限流阈值 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); 限流阈值类型,QPS 或线程数模式 rule.setLimitApp("default"); ruleList.add(rule); FlowRuleManager.loadRules(ruleList); return "初始化策略成功..."; } }
效果与上面一样。
- 采用注解化方式(推荐)
把步骤1代码重构。
/** * 使用注解实现 */ @RequestMapping("/insertUser") @SentinelResource(value = INSERTUSER_KEY, blockHandler = "insertUserBlockHandler") public String insertUser(){ return "insertUser接口..."; } /** * 回调方法, */ public String insertUserBlockHandler(BlockException e){ return "当前访问人数太多,请稍后重试!" + e.toString(); }
但是数量QPS数量只能在控制台更改。
-
-
Sentinel 控制台形式配置
1 下载jar包,解压到文件夹
网址:https://github.com/alibaba/Sentinel/releases
2 启动控制台
//jar命令启动,自定义端口号(控制台本身是一个SpringBoot项目) java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
//或者直接默认启动,端口号8080 java -jar sentinel-dashboard-1.8.0.jar
3 mumber-producer向控制台注册
server: port: 8080 spring: application: name: mumber-producer #服务名程 cloud: nacos: discovery: server-addr: localhost:8848 # nacos默认端口地址为8848 enabled: true sentinel: transport: dashboard: localhost:8718/ #sentinel-dashboard 地址 eager: true #默认为false,设置为true限流才有效果
4 编写接口
@RequestMapping("/getOrderConsle") public String getOrderConsle(){ return "getOrderConsle接口..."; }
5 通过浏览器访问localhost:8718 进入控制台 (默认用户名密码是 sentinel/sentinel)
6 添加一个接口限流规则
此时访问 localhost:8080/getOrderConsole 太频繁时会看到如下提示:
7 自定义提示
修改上述java代码
@RequestMapping("/getOrderConsle") @SentinelResource(value = "getOrderConsle", blockHandler = "insertUserBlockHandler") public String getOrderConsle(){ return "getOrderConsle接口..."; } /** * 回调方法 */ public String insertUserBlockHandler(BlockException e){ return "当前访问人数太多,请稍后重试!"; }
修改限流规则,在5的基础上,取消斜杠即可(此处不再截图),此时访问接口
此处注意:控制台限流规则并非持久化,重启项目会失效,需重新添加先流规则。
五、 @SentinelResource的使用
上面步骤四已经接触了SentinelResource的使用,此处对此作详细讲解。
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。其主要参数如下:
BlockException包含很多个子类,分别对应不同的场景:
此处可以理解为除了上图列举的那些异常,其他异常由fallback处理。fallback代码配置如下:
/**
* blockHandler 用法上面已举例
*/
@RequestMapping("/user")
@SentinelResource(value = "user", blockHandler = "fail",fallback = "fallback")
public String user(Integer age) {
if (age % 3 == 0){
throw new RuntimeException();
}
return "年龄:" + age;
}
public String fallback(Integer age) {
log.error("{}", age);
return "user接口发生异常了";
}
public String fail(String name,Integer age, BlockException e){
return "user接口参数被限流了,请稍后重试!";
}
blockHandler 和 fallback ,默认和原方法写在一个类中,如需写在其他类中,需用到blockHandlerClass和fallbackClass。
@Slf4j
/**
* 此类可以不注入到spring容器
*/
public class BlockHandlerAndFallBackImpl {
/**
* 注意这里必须使用static修饰方法
* 参数、返回类型,必须与原类一致
*/
public static String fail(Integer age, BlockException ex) {
log.error("{}", age);
return "接口参数被限流了,请稍后重试!";
}
public static String fallback(Integer age) {
log.error("{}", age);
return "接口发生异常了...";
}
}
@RestController
@Slf4j
public class MumberController2 {
@RequestMapping("/user")
@SentinelResource(value = "user", blockHandlerClass = BlockHandlerAndFallBackImpl.class, blockHandler = "fail", fallbackClass = BlockHandlerAndFallBackImpl.class, fallback = "fallback")
public String user(Integer age) {
if (age % 3 == 0){
throw new RuntimeException();
}
return "年龄:" + age;
}
}
六、Sentinel规则持久化
可以发现上面的演示都没有依赖数据库,那配置的规则数存到哪里的呢?答案是:内存中 ,这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。
官方文档:https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel
Sentinel提供了以下三种模式,其中Pull模式和Push模式可以保证规则持久化:
- 原始模式:上述的演示过程即为原始模式,不可用于生产环境。
- 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
- 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。
由于生产环境使用的是第三种方式:推模式,所以接下来采用nacos注册中心演示持久化规则,这种方式的交互切断了Sentinel客户端和 Sentinel Dashboard的联系。它的交互方式变为: 配置中心控制台/Sentinel 控制台===>配置中心(Nacos)===>Sentinel客户端。演示过程在步骤七。
七、nacos整合sentinel规则持久化
1 SpringBoot 整合 nacos-sentinel 持久化依赖
<!-- SpringBoot 整合 nacos-sentinel 持久化 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.0</version>
</dependency>
2 添加配置
server:
port: 8080
spring:
application:
name: mumber-producer #服务名程
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos默认端口地址为8848
enabled: true
sentinel:
transport:
dashboard: localhost:8718 #sentinel-dashboard 地址
eager: true
#nacos整合sentinel持久化
datasource:
- nacos:
server-addr: localhost:8848 #nacos连接地址
group-id: DEFAULT_GROUP #nacos连接分组
rule-type: flow #路由存储规则flow、degrade、param-flow、gw-flow
data-id: ${spring.application.name}-sentinel #读取配置文件的 data-id
data-type: json #读取配置文件类型为 JSON 和 XML
3 创建接口
//集成 nacos 实现 sentinel 流控规则的持久化
@RequestMapping("/getOrderSentinel")
public String getOrderSentinel() {
return "getOrderSentinel接口...";
}
4 访问nacos控制台,创建配置
配置内容:
[
{
"resource": "/getOrderSentinel",
"limitApp": "default",
"grade": 1,
"count": 10,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
resource: 资源名,即限流规则的作用对象
limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
grade:限流阙值类型(QPS 或并发线程数);0 代表根据并发数量来限流, 1 代表 QPS 来进行流量控制
count:限流阙值
strategy:调用关系限流策略
5 启动项目
6 回到sentinel控制台,看到刚配置的规则已生效
7 通过nacos修改该阈值,修改为100,然后发布
然后回到sentinel控制台,查看刚才的阈值,发现已变化为100,则已实现通过 nacos 配置中心修改限流规则。