Alibaba Sentinel功能入门与配置持久化
文章目录
1.准备工作
-
sentinel dashboard jar包下载,这里结合项目SpringCloud版本,选用官网推荐的1.8.1
-
适当版本的JDK(可参考dashboard包内文档建议)
-
一个微服务项目(我这里使用的是之前自己写的springcloud+nacos项目,源代码参见SpringCloudAlibaba: SpringCloudAlibaba练习项目 (gitee.com),思路参见https://blog.csdn.net/qq_61603262/article/details/127345832),具体版本如下:
<druid.version>1.2.13</druid.version> <mysql.version>8.0.30</mysql.version> <spring-boot-starter-data-jdbc.version>2.7.4</spring-boot-starter-data-jdbc.version> <spring-boot-starter-jdbc.version>2.7.4</spring-boot-starter-jdbc.version> <mybatis.version>2.1.4</mybatis.version> <springdoc.version>1.6.11</springdoc.version>
2.整合与启动
2.1项目引入依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
2.2项目yaml配置
spring:
cloud:
sentinel:
enabled: true
transport:
dashboard: localhost:8080 #这里配置的是dashboard对应的端口号,项目与jar包运行端口保持一致即可
2.3控制台配置项
控制台的一些特性可以通过配置项来进行配置,配置项主要有两个来源:System.getProperty()
和 System.getenv()
,同时存在时后者可以覆盖前者。
通过环境变量进行配置时,因为不支持
.
所以需要将其更换为_
。
配置项 | 类型 | 默认值 | 最小值 | 描述 |
---|---|---|---|---|
auth.enabled | boolean | true | - | 是否开启登录鉴权,仅用于日常测试,生产上不建议关闭 |
sentinel.dashboard.auth.username | String | sentinel | - | 登录控制台的用户名,默认为 sentinel |
sentinel.dashboard.auth.password | String | sentinel | - | 登录控制台的密码,默认为 sentinel |
sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 |
sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 |
sentinel.dashboard.autoRemoveMachineMillis | Integer | 0 | 300000 | 距离最近心跳时间超过指定时间是否自动删除失联节点,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 |
server.servlet.session.cookie.name | String | sentinel_dashboard_cookie | - | 控制台应用的 cookie 名称,可单独设置避免同一域名下 cookie 名冲突 |
配置示例:
- 命令行方式:
java -Dsentinel.dashboard.app.hideAppNoMachineMillis=60000
- Java 方式:
System.setProperty("sentinel.dashboard.app.hideAppNoMachineMillis", "60000");
- 环境变量方式:
sentinel_dashboard_app_hideAppNoMachineMillis=60000
2.4启动项目与sentinel-dashboard.jar
java -jar sentinel-dashboard.jar
2.5测试
访问项目中任意服务,在dashboard能够看到该项目的spring.application.name,打开选项卡后可以看到相关访问统计。
3.可能出现的异常
3.1.The Bean Validation API is on the classpath but no implementation could be found
***************************
APPLICATION FAILED TO START
***************************
Description:
The Bean Validation API is on the classpath but no implementation could be found
Action:
Add an implementation, such as Hibernate Validator, to the classpath
解决方案:
引入依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
如不可行,尝试引入springboot自带的validation:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3.2.sentinel-dashboard.jar启动:java.lang.IllegalStateException: Cannot load configuration class:com.alibaba.csp.sentinel.dashboard.DashboardApplication
启动时换用下述命令(加入启动参数):
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/sun.net.util=ALL-UNNAMED -jar sentinel-dashboard-1.8.1.jar
4.具体使用配置
4.1限流规则
-
普通限流
-
关联限流:当对A访问的QPS/线程数达到阈值时,对B限流。使用时需要配置B的限流规则。即给谁限流就配置谁的限流规则(配置访问优先级较低的服务)。
-
链路限流:限制来自不同路径对同一服务的访问。需要设置web-context-unify: false才会生效。限制那个路径的QPS就配置哪个路径。
-
热点参数限流:根据请求携带参数限流。
注意:该配置只对添加了@SentinelResource注解的资源生效
4.2限流结果
-
快速失败:抛出FlowException,返回429错误
-
warm up:同快速失败,但在抛出异常的同时,阈值会逐渐增大(默认从最大值的三分之一开始)。常用于服务冷启动。
参数:预热时长
-
排队等待:接到超过单机阈值的请求不会立即抛出异常,把请求放进队列延迟进行直至超时或被执行。
5.服务降级、隔离与熔断
5.1consumer端Feign整合Sentinel
5.1.1引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
5.1.2yaml添加配置
feign:
sentinel:
enabled: true
5.1.3编写失败后的返回方法FallbackFactory
这里要注意的是,FallbackFactory有两个类,即OpenFeign的和Hystrix的,这里我们要继承的是Hystrix的。
package com.zjy.consumer9000.config;
import com.zjy.consumer9000.service.DeptFeignService;
import feign.hystrix.FallbackFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DeptFallbackFactory implements FallbackFactory<DeptFeignService> {
@Override
public DeptFeignService create(Throwable cause) {
return new DeptFeignService() {
@Override
public List<Map> getAll() {
Map map=new HashMap();
map.put("msg","unknown exception");
List<Map> list=new ArrayList<>();
list.add(map);
return list;
}
@Override
public Map queryById(Integer departmentId) {
Map map=new HashMap();
map.put("msg","not found");
return map;
}
@Override
public List<Map> queryByName(String departmentName) {
Map map=new HashMap();
map.put("msg","there's no such a department");
List<Map> list=new ArrayList<>();
list.add(map);
return list;
}
};
}
}
注册bean
package com.zjy.consumer9000.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FallbackFactoryConfig {
@Bean
public DeptFallbackFactory getDeptFallbackFactory(){
return new DeptFallbackFactory();
}
}
在FeignClient加入注解以激活fallbackFactory
@FeignClient(value = "dataProvider8000",fallbackFactory = DeptFallbackFactory.class)
5.1.4修改provider端的Controller
@Operation(summary = "通过id查找部门")
@GetMapping("/query/id/{departmentId}")
@SentinelResource
public Department queryById(@PathVariable Integer departmentId){
Department dept=departmentService.getDepartmentById(departmentId);
if(dept.getDepartmentName()==null)
throw new NullPointerException("there's no such a dept!"){
};
return dept;
}
这里笔者只改了其中一个方法来验证,其他类似
5.2线程隔离
5.2.1信号量隔离(默认)
信号量隔离的其实很简单,就是在Constumer和Provider之间加上一个的[消息队列,在指定的时间内只允许指定的请求量能够访问到Provider,如果请求超出,则返回托底数据。
5.2.2线程池隔离
线程池是通过限制线程数,来对资源使用进行限制,而不是隔离。限制资源使用,防止系统过载。
使用线程池的优点
- 使用线程池隔离可以完全隔离依赖的服务,请求线程可以快速找回。
- 当线程池出现问题时,线程池隔离是独立的,不会影响其他服务的接口。
- 当失败的服务变得可用时,线程池将清理并立即修复,而不需要一个漫长的等待。
- 独立的线程池提高了并发性。
使用线程池的缺点
线程池的主要缺点是增加了cpu的开销,每个命令涉及到排队,调度和上下文切换都是在一个独立的线程上运行的。
5.3线程熔断
5.3.1慢调用比例 (SLOW_REQUEST_RATIO)
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
Sentinel默认统计的RT上限是4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置
5.3.2异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO)
当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
5.3.3异常数 (DEGRADE_GRADE_EXCEPTION_COUNT)
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
6.统一处理异常
6.1方法一:实现sentinel内置接口
sentinel正常工作情况下抛出的异常:BlockException(FlowException、ParamFlowException、DegradeException、AuthorityException、SystemBlockException )。相应的,sentinel提供了处理该异常的接口BlockExceptionHandler。我们只需要实现该接口就可以完成对该异常的自定义抛出。代码如下
package com.zjy.dataprovider8000.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class SentinelBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
BlockException e) throws Exception {
String msg="未知错误";
int status=429;
if (e instanceof FlowException) msg = "限流异常";
if (e instanceof DegradeException)msg = "服务降级";
if (e instanceof ParamFlowException)msg = "热点限流";
httpServletResponse.getWriter().print(msg);
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.setStatus(status);
System.err.println(msg);
}
}
6.2方法二:通过SpringCloud自带注解
思路
- 新建一个ExceptionHandler类,用@RestControllerAdvice(basePackages = “com.xxx.controller”)注解指明要处理的包
- 在方法上用@ExceptionHandler(value= Exception.class)指明要捕捉的异常类
package com.zjy.dataprovider8000.config;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice(basePackages = "com.zjy.dataprovider8000.controller")
public class SentinelExceptionHandler {
@ResponseBody
@ExceptionHandler(value= Exception.class)
public Map 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 + "}");
HashMap map=new HashMap();
map.put("msg",msg);
return map;
}
}
这里我们没有用网关来限制权限,之后将全部交由安全框架解决
7.生产环境下配置持久化(整合nacos的push模式)
7.1规则属性一览
流量规则(FlowRule)
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 模式(1)或并发线程数模式(0) | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
熔断降级规则(DegradeRule)
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为 s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
热点参数规则(ParamFlowRule
)
更多操作Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,必填 | |
count | 限流阈值,必填 | |
grade | 限流模式 | QPS 模式 |
durationInSec | 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 | 1s |
controlBehavior | 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 | 快速失败 |
maxQueueingTimeMs | 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 | 0ms |
paramIdx | 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置 | |
paramFlowItemList | 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型 | |
clusterMode | 是否是集群参数流控规则 | false |
clusterConfig | 集群流控相关配置 |
系统保护规则 (SystemRule)
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则包含下面几个重要的属性:
Field | 说明 | 默认值 |
---|---|---|
highestSystemLoad | load1 触发值,用于触发自适应控制阶段 | -1 (不生效) |
avgRt | 所有入口流量的平均响应时间 | -1 (不生效) |
maxThread | 入口流量的最大并发数 | -1 (不生效) |
qps | 所有入口资源的 QPS | -1 (不生效) |
highestCpuUsage | 当前系统的 CPU 使用率(0.0-1.0) | -1 (不生效) |
7.2硬编码实现
流量规则(FlowRule)
private void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule(resourceName);
// set limit qps to 20
rule.setCount(20);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
熔断降级规则 (DegradeRule)
private void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
// set threshold RT, 10 ms
rule.setCount(10);
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
热点参数规则(ParamFlowRule
)
private void initDParamFlowRule() {
ParamFlowRule rule = new ParamFlowRule(resourceName)
.setParamIdx(0)
.setCount(5);
// 针对 int 类型的参数 PARAM_B,单独设置限流 QPS 阈值为 10,而不是全局的阈值 5.
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B))
.setClassType(int.class.getName())
.setCount(10);
rule.setParamFlowItemList(Collections.singletonList(item));
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
系统保护规则 (SystemRule)
private void initSystemRule() {
List<SystemRule> rules = new ArrayList<>();
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(10);
rules.add(rule);
SystemRuleManager.loadRules(rules);
}
7.3json实现
引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
yaml
spring:
cloud:
sentinel:
datasource:
# 名称随意
flow:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
# 规则类型,取值见:
# org.springframework.cloud.alibaba.sentinel.datasource.RuleType
rule-type: flow
degrade:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
rule-type: degrade
system:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-system-rules
groupId: SENTINEL_GROUP
rule-type: system
authority:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-authority-rules
groupId: SENTINEL_GROUP
rule-type: authority
param-flow:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-param-flow-rules
groupId: SENTINEL_GROUP
rule-type: param-flow
流量规则(FlowRule)
[
{
// 资源名
"resource": "/test",
// 针对来源,若为 default 则不区分调用来源
"limitApp": "default",
// 限流阈值类型(1:QPS;0:并发线程数)
"grade": 1,
// 阈值
"count": 1,
// 是否是集群模式
"clusterMode": false,
// 流控效果(0:快速失败;1:Warm Up(预热模式);2:排队等待)
"controlBehavior": 0,
// 流控模式(0:直接;1:关联;2:链路)
"strategy": 0,
// 预热时间(秒,预热模式需要此参数)
"warmUpPeriodSec": 10,
// 超时时间(排队等待模式需要此参数)
"maxQueueingTimeMs": 500,
// 关联资源、入口资源(关联、链路模式)
"refResource": "rrr"
}
]
熔断降级规则 (DegradeRule)
[
{
// 资源名
"resource": "/test1",
"limitApp": "default",
// 熔断策略(0:慢调用比例,1:异常比率,2:异常计数)
"grade": 0,
// 最大RT、比例阈值、异常数
"count": 200,
// 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
"slowRatioThreshold": 0.2,
// 最小请求数
"minRequestAmount": 5,
// 当单位统计时长(类中默认1000)
"statIntervalMs": 1000,
// 熔断时长
"timeWindow": 10
}
]
热点参数规则(ParamFlowRule
)
[
{
// 资源名
"resource": "/test1",
// 限流模式(QPS 模式,不可更改)
"grade": 1,
// 参数索引
"paramIdx": 0,
// 单机阈值
"count": 13,
// 统计窗口时长
"durationInSec": 6,
// 是否集群 默认false
"clusterMode": 默认false,
//
"burstCount": 0,
// 集群模式配置
"clusterConfig": {
//
"fallbackToLocalWhenFail": true,
//
"flowId": 2,
//
"sampleCount": 10,
//
"thresholdType": 0,
//
"windowIntervalMs": 1000
},
// 流控效果(支持快速失败和匀速排队模式)
"controlBehavior": 0,
//
"limitApp": "default",
//
"maxQueueingTimeMs": 0,
// 高级选项
"paramFlowItemList": [
{
// 参数类型
"classType": "int",
// 限流阈值
"count": 222,
// 参数值
"object": "2"
}
]
}
]
系统保护规则 (SystemRule)
[
{
// RT
"avgRt": 1,
// CPU 使用率
"highestCpuUsage": -1,
// LOAD
"highestSystemLoad": -1,
// 线程数
"maxThread": -1,
// 入口 QPS
"qps": -1
}
]