SpringCloud -- Sentinel
- Sentinel 概述
- Sentinel 的安装与启动
- Sentinel 微服务创建
- Sentinel 规则
- 流控规则
- 热点规则
- 熔断规则
- 授权规则
- Sentinel 其他常用功能
- 规则持久化
- 自定义异常
Sentinel 概述
sentinel 官方文档
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel 的安装与启动
下载地址: 官方Github地址
下载解压完成后,进入对应文件夹执行下列指令启动服务
java -jar <对应版本jar包名称>
浏览器访问localhost:8080
,即可查看 sentinel 控制面板,登录用户名密码均为 sentinel
Sentinel 微服务创建
在 SpringCloudAlibabaDemo 父项目中(详见Nacos)创建子项目
注:旧版 spring-cloud-alibaba 很多 sentinel 功能无法使用,此处使用的版本如下,需要修改父项目 pom 依赖版本:
-
spring boot 2.3.2.RELEASE
-
spring cloud Hoxton.SR9
-
spring cloud alibaba 2.2.5.RELEASE
子项目中引入依赖
<dependencies>
<!-- SpringCloud alibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud alibaba sentinel-datasource-nacos:用于持久化 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- SpringCloud alibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringBoot整合web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
编写配置文件进行设置,将微服务注册进 nacos,配置 Sentinel 服务的端口
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: <Your_Nacos_IP>:8848
sentinel:
transport:
#配置Sentinel Dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始一依次+1扫描,知道找到未占用的端口
port: 8719
#关闭链路合并
web-context-unify: false
management:
endpoints:
web:
exposure:
include: '*'
编写启动类
@SpringBootApplication
public class SentinelStart8401 {
public static void main(String[] args) {
SpringApplication.run(SentinelStart8401.class);
}
}
编写业务层代码
@Service
public class SentinelService {
@SentinelResource("getMessage")
public String getMessage(String msg) {
return "This is " + msg;
}
}
编写控制类代码用于测试,@SentinelResource
注解用于标注接口在 sentinel dashboard 中的名称
@RestController
@RequestMapping("/sentinel")
public class SentinelController {
@Autowired
SentinelService sentinelService;
@GetMapping("/test01")
public String testSentinel01() throws InterruptedException {
// Thread.sleep(3000); // 测试限流并发数时阻塞请求的线程
return sentinelService.getMessage("test01");
}
@GetMapping("/test02")
public String testSentinel02() {
return sentinelService.getMessage("test02");
}
@GetMapping("/test03/{id}")
public String testSentinel03(@PathVariable("id") String id) {
// 模拟异常,测试熔断
// if ("3".equals(id)) {
// throw new RuntimeException("构造异常产生...");
// }
return sentinelService.getMessage("test03>>>id=" + id);
}
}
启动项目,访问相应的接口,即可在 sentinel 控制面板中查看到相应的服务
注:sentinel 默认是懒加载模式,故理想状态下需要请求一次@SentinelResource(value=“xxx”)
注解的接口才能在控制台看到服务
Sentinel 规则
在 sentinel 控制台左侧点击“簇点链路”,即可看到被监控的每一个接口(在本文案例中为 test01 和 test02),每条链路后面都有流控、熔断、热点、授权四个操作按钮,点击对应按钮即可为该接口链路设置对应的规则。
注:下述所有的规则验证可以通过 Apifox 进行测试(官方网站),亲测好用!
流控规则
点击左侧流控规则
菜单,然后点击右上角+新增流控规则
即可为链路配置流控规则
流控规则中各个属性配置的功能如下:
-
资源名:对应的规则适用的链路名称(即一条访问链路通道);
-
针对来源:可以配置对调用者进行限流,默认为 default,即不区分来源;
-
阈值类型
-
QPS:即限制资源的每秒的访问次数;
-
并发线程数:即限制资源的并发访问量;
-
-
单机阈值:为阈值类型设置限流规则生效的上限;
-
是否集群:是否为集群模式;
-
流控模式
-
直接:当前资源达到限流条件时,对自身资源进行限流;
-
关联:当关联的资源达到限流条件时,对自身资源进行限流;
-
链路:当前资源达到限流条件时,对配置的入口资源链路进行限流;
-
-
流控效果
-
快速失败:达到设置的阈值之后,新到来的请求将会被立即拒绝并抛出异常(默认);
-
Warm up:预热模式,拒绝超出设置阈值的请求,但阈值是一个动态变化的值。当大量请求接入时该阈值将会存在一个预热的过程,逐步增加到我们所设置的阈值,阈值的初始值根据冷却因子 codeFactor(默认为 3,即初始值为
设定阈值/3
)确定,可以对预热时长进行设置; -
排队等待:当请求数超过阈值时,将会让请求排入队列,并按照我们所设置的阈值进行请求的处理,未被处理的请求将在队列中持续等待,若等待时间超过所设置的超时时间,该请求将被拒绝;
-
热点规则
上述的流控规则是对某个资源的请求量进行监视并依据设置的阈值进行流量控制,而热点监控则是对资源的某些热点参数进行流量控制。
点击左侧热点规则
菜单,然后点击右上角+新增热点限流规则
即可为链路配置热点规则
热点规则中各个属性配置的功能如下:
-
资源名:进行热点规则控制的资源名称;
-
参数索引:热点参数的索引,从 0 开始;
-
单机阈值:对该资源进行整体的流量控制,同流控规则;
-
统计窗口时长:设置请求数量统计的时间窗口;
-
参数例外项
-
参数类型:指定要进行流控的热点参数的参数类型;
-
参数值/限流阈值:设置热点参数值,并设置该参数的限流阈值,可针对不同的参数值设置多组;
-
熔断规则
熔断降级主要用于解决服务雪崩问题,通过统计服务器调用的异常比例、慢请求比例,若超出设置的阈值则拦截一切访问请求,直到服务恢复时重新放行。
点击左侧熔断规则
菜单,然后点击右上角+新增熔断规则
即可为链路配置热点规则
熔断规则中各个属性配置的功能如下:
-
资源名:进行熔断规则控制的资源名称;
-
熔断策略
-
慢调用比例:当慢调用请求在统计时长内的比例大于设置的比例阈值时,熔断当前资源。业务的响应时间大于设置的最大 RT 则认为是慢调用;
-
异常比例:当异常请求的比例大于所设置的异常比例时,熔断当前资源;
-
异常数:当异常请求的数量大于所设置的异常数时,熔断当前资源;
-
-
熔断时长:服务熔断的时间;
-
最小请求数:统计时长内触发熔断的最小请求数量;
-
统计时长:统计判断熔断参数的时间窗口;
授权规则
授权规则可以根据请求的来源进行限制,通过白名单和黑名单构建授权规则。
点击左侧授权规则
菜单,然后点击右上角+新增授权规则
即可为链路配置授权规则
授权规则中各个属性配置的功能如下:
- 资源名:进行授权规则控制的资源名称;
- 流控应用:限制调用方的名称,多个调用方用英文逗号隔开;
- 授权类型
- 白名单:只允许白名单应用的请求访问相应资源;
- 黑名单:只拒绝黑名单应用的请求访问相应资源;
对于授权规则,需要开发者自己手写一个类实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser
类并实现parseOrigin
方法,该方法返回请求的来源字符串(即我们设置的流控应用名称)。获取来源的逻辑由开发者自己定义,可以从请求参数或请求头等获得。下述代码获取请求头中的 zzz 属性作为来源。
@Component
public class HeadOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String zzz = httpServletRequest.getHeader("zzz");
if (StringUtils.isBlank(zzz)){
zzz = "null";
}
return zzz;
}
}
若 zzz 属性对应的值为我们设置的流控应用名,则根据黑白名单进行相应的处理。
Sentinel 其他常用功能
规则持久化
sentinel 对规则的管理由三种模式:
-
原始模式:sentinel 的默认模式,将规则保存在内存当中,重启服务会丢失;
-
pull 模式:sentinel dashboard 将规则推送给 sentinel 客户端,客户端将规则配置保存在本地;
-
push 模式:sentinel dashboard 将规则推送到远程控制中心如 nacos,sentinel 客户端监听 nacos 并获取配置变更的推送消息,完成本地配置更新;
下边配置规则持久化到 Nacos:
-
首先下载 sentinel 的源码,下载完成后使用编译器打开;
-
修改 sentinel-dashboard 模块的 pom 文件,注释掉对应行代码
-
将 test 文件夹下的
com.alibaba.csp.sentinel.dashboard.rule.nacos
文件夹复制到 main 文件夹下的com.alibaba.csp.sentinel.dashboard.rule
文件夹-
FlowRuleNacosProvider.java
:从 nacos 配置中心动态获取流控规则; -
FlowRuleNacosPublisher.java
:上传动态获取流控规则到 nacos 配置中心; -
NacosConfig.java
:nacos 配置; -
NacosConfigUtils.java
:流控规则 nacos 命名以及分组相关配置;
-
-
在
com.alibaba.csp.sentinel.dashboard.rule.nacos
路径下创建NacosProperties.java
参数配置类,从配置文件中获取参数@Component @ConfigurationProperties(prefix = "sentinel.nacos") public class NacosProperties { /** * nacos地址 */ private String serverAddr; /** * nacos命名空间 */ private String namespace; public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } }
-
修改
NacosConfig.java
@Configuration public class NacosConfig { @Autowired NacosProperties nacosProperties; @Bean public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public ConfigService nacosConfigService() throws Exception { // 修改前 // return ConfigFactory.createConfigService("localhost"); // 修改后 Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, nacosProperties.getServerAddr()); properties.put(PropertyKeyConst.NAMESPACE, nacosProperties.getNamespace()); return ConfigFactory.createConfigService(properties); } }
-
修改
com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2
文件,指定对应的 Bean@Autowired // @Qualifier("flowRuleDefaultProvider") @Qualifier("flowRuleNacosProvider") private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider; @Autowired // @Qualifier("flowRuleDefaultPublisher") @Qualifier("flowRuleNacosPublisher") private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
-
修改
webapp/resources/app/scripts/directives/sidebar/sidebar.html
文件,找到下述代码进行修改(注释掉的为旧代码)<!-- <li ui-sref-active="active" ng-if="!entry.isGateway">--> <!-- <a ui-sref="dashboard.flowV1({app: entry.app})">--> <!-- <i class="glyphicon glyphicon-filter"></i> 流控规则</a>--> <!-- </li>--> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li>
-
修改
webapp/resources/app/scripts/controllers/identity.js
文件,将FlowServiceV1 改成 FlowServiceV2
-
还是步骤8描述的文件,修改其中的
saveFlowRule
方法function saveFlowRule() { if (!FlowService.checkRuleValid(flowRuleDialogScope.currentRule)) { return; } FlowService.newRule(flowRuleDialogScope.currentRule).success(function (data) { if (data.code === 0) { flowRuleDialog.close(); // let url = '/dashboard/flow/' + $scope.app; //修改前 let url = '/dashboard/v2/flow/' + $scope.app; //修改后 $location.path(url); } else { alert('失败:' + data.msg); } }).error((data, header, config, status) => { alert('未知错误'); }); }
-
添加 nacos 配置,启动 sentinel dashboard 项目即可,记得清理一下浏览器缓存
#nacos配置 sentinel.nacos.serverAddr=<nacos-server-ip>:8848 sentinel.nacos.namespace=bafd6448-e6f8-4b8a-8c6e-1b91a15389d4
-
修改 sentinel 客户端服务配置,添加持久化的配置项,启动项目
server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 47.92.146.85:8848 sentinel: transport: #配置Sentinel Dashboard地址 dashboard: localhost:8080 #默认8719端口,假如被占用会自动从8719开始一依次+1扫描,知道找到未占用的端口 port: 8719 datasource: flow: nacos: server-addr: 47.92.146.85:8848 namespace: bafd6448-e6f8-4b8a-8c6e-1b91a15389d4 dataId: ${spring.application.name}-flow-rules groupId: SENTINEL_GROUP dataType: json rule-type: flow #关闭链路合并 web-context-unify: false management: endpoints: web: exposure: include: '*'
-
点击左侧
流控规则
菜单,然后点击右上角+新增流控规则
即可为链路配置流控规则,完成后可在 nacos 对应的命名空间下看到相应的流控配置;对配置进行修改并发布后,也可以观察到 dashboard 配置动态改变。
自定义异常
一般情况下,发生限流、降级、熔断、权限拦截时,都会抛出异常给调用方,如果需要自定义异常的返回结果则需要实现BlockExceptionHandle
接口。
Sentinel 为我们提供了很多基本的异常类:
异常 | 说明 |
---|---|
FlowException | 一般流控异常 |
ParamFlowException | 热点参数限流异常 |
DegradeException | 降级异常 |
AuthorityException | 授权规则异常 |
SystemBlockException | 系统规则异常 |
下述代码可以对不同异常情况下的返回信息以及响应码进行修改
@Component
public class MyException 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 ParamFlowException){
msg = "请求被热点参数限流了";
} else if (e instanceof DegradeException){
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 + "}");
}
}