SpringCloud-Sentinel
SpringCloud-Sentinel
cmyang1. Sentinel简介
官方地址:https://github.com/alibaba/Sentinel/
中文文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
2. Sentinel控制台(1.8.1)
下载地址:https://github.com/alibaba/Sentinel/releases
中文文档:https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
下载后的文件:sentinel-dashboard-1.8.1.jar
启动命令:nohup java -Dserver.port=9090 -Dcsp.sentinel.dashboard.server=localhost:9090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar &
注:端口号根据具体情况修改,jar包的名称根据版本号修改
浏览器访问控制台:http://localhost:9090 默认账号密码 :sentinel/sentinel
3. 客户端
以order-service为例
3.1 添加依赖
1 | <dependency> |
3.2 添加配置文件
1 | spring: |
3.3 测试
启动控制台jar
启动order-service
多次访问http://127.0.0.1:8080/order-service/api/v1/order/demo/sayHello?name=aacopy
查看控制台order-service的实时监控项
4. 流控配置
4.1 基于QPS
根据请求地址配置qps流控规则,当 QPS 超过某个阈值的时候,则采取措施进行流量控制
测试流控规则
- 快速刷新http://127.0.0.1:8080/order-service/api/v1/order/demo/sayHello?name=aacopy
- 提示“Blocked by Sentinel (flow limiting)”表示流控生效
查看编辑流控规则,在左侧边栏找到流控规则进行操作
4.2 基于并发线程数
用于保护业务线程池不被慢调用耗尽,如果超出阈值,新的请求会被立即拒绝,类似于信号量隔离。
- 编写测试代码
1 |
|
重启order-service服务
打开sentinel控制台,这时候发现之前添加的流控规则没有了,是因为流控规则会下发到微服务,微服务如果重启,则流控规则会消失,可以持久化配置解决,后面会讲到
浏览器请求一次http://127.0.0.1:8080/order-service/api/v1/order/demo/sayHi?name=aacopy
在簇点链路中找到**/api/v1/order/demo/sayHi**,点击流控
再次访问sayHi链接,3秒内访问超过2次,则会看到限流提示Blocked by Sentinel (flow limiting)
5. 熔断降级
对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一,熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置
文档:https://github.com/alibaba/Sentinel/wiki/熔断降级
5.1 熔断策略
5.1.1 慢调用比例
TODO
5.1.2 异常比例
- 编写测试代码
1 | Random random = new Random(); |
访问地址:http://127.0.0.1:8080/order-service/api/v1/order/demo/exceptionProportion
打开控制台,在簇点链路中找到,点击降级
- 快速刷新访问地址,触发降级,提示”*Blocked by Sentinel (flow limiting)*“
6. 自定义异常
实现BlockExceptionHandler并且重写handle方法
1 |
|
添加限流,降级等操作,触发流控测试
7. feign和sentinel整合
- 添加配置项
1 | #开启Feign对Sentinel的支持 |
- 创建容错类
创建fallback文件夹编写实现类GoodsServiceFallback
1 |
|
- 配置feign容错类
在feignClient接口上的注解@FeignClient增加fallback参数
1 |
注意:
当前最新springCloud版本2020.0.4,无法兼容此功能,会报错
1
Requested bean is currently in creation: Is there an unresolvable circular reference?
如果要用到此功能需要将springCloud版本降到Hoxton.SR9及以下
https://github.com/alibaba/spring-cloud-alibaba/issues/1974
https://github.com/alibaba/Sentinel/issues/2065
8. Nacos规则配置持久化
8.1 需求
Sentinel默认在控制台(Dashboard)中添加流控降级规则后,将配置规则信息推送给Sentinel客户端,配置的规则记录在内存中,如果客户端重启,规则也就丢失。
Sentinel的流控规则持久化到nacos中,并且在Sentinel控制台中修改流控规则,需要同步修改nacos的值,并且服务立即生效
8.2 官方相关文档:
动态规则扩展:https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95
在生产环境中使用 Sentinel:https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel
Sentinel 控制台(集群流控管理):https://github.com/alibaba/Sentinel/wiki/Sentinel-%E6%8E%A7%E5%88%B6%E5%8F%B0%EF%BC%88%E9%9B%86%E7%BE%A4%E6%B5%81%E6%8E%A7%E7%AE%A1%E7%90%86%EF%BC%89#%E8%A7%84%E5%88%99%E9%85%8D%E7%BD%AE
8.3 方案:DataSource 扩展
官方推荐:通过控制台设置规则后将规则推送到统一的规则中心(Nacos、Zookeepe),客户端实现 ReadableDataSource
接口端监听规则中心实时获取变更
客户端部分:(实现上图 2,3步骤)
最简单的方式是继承 AbstractDataSource 抽象类,该抽象类已经实现了ReadableDataSource接口,在其构造方法中添加监听器,并实现
readSource()
从指定数据源读取字符串格式的配置数据。目前官方已经实现了基于Nacos的数据源,NacosDataSource,这个类继承了抽象类
AbstractDataSource
。客户端只需要引入
sentinel-datasource-nacos
依赖通过Nacos配置参数创建
NacosDataSource
数据源对象,并且将该数据源注册到指定的规则管理器中,如下将Nacos数据源注册到流控规则管理器中1
2ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, parser);
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());该操作可以放在spring容器启动完成后,执行注册操作。
控制台部分:(实现上图1步骤)
Sentinel 控制台提供
DynamicRulePublisher
(推送规则) 和DynamicRuleProvider
(拉取规则)接口用于实现应用维度的规则推送和拉取官方提供了流程规则的示例代码,参照示例代码,对降级,热点,系统,权限做相应的改造
官方提供了 Nacos、ZooKeeper 和 Apollo 的推送和拉取规则实现示例(位于
test
目录下)以 Nacos 为例,只需在 FlowControllerV2 中指定对应Nacos的 bean 即可开启 Nacos 适配。前端页面需要手动切换,或者修改前端路由配置(sidebar.html 流控规则路由从 dashboard.flowV1 改成 dashboard.flow 即可,簇点链路页面对话框需要自行改造
1
2
3
4
5
6
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;默认 Nacos 适配的 dataId 和 groupId 约定如下:
- groupId:
SENTINEL_GROUP
- 流控规则 dataId:
{appName}-flow-rules
,比如应用名为 appA,则 dataId 为 appA-flow-rules - 这些约定配置
NacosConfigUtil
类中定义 - 扩展的降级,热点,系统,权限等这些配置就需要在这里定义
- groupId:
官方还提供了String和流控规则对象转换类,在
NacosConfig
类中,在做扩展时也需要将其他规则转换器注册到spring容器中,在controller操作时需要用到
8.4 控制台Dashboard改造
8.4.1 下载控制台源码
根据项目具体版本获取对应的源码,本笔记使用1.7.1版本
github地址:https://github.com/alibaba/Sentinel
克隆:https://github.com/alibaba/Sentinel.git
切换到tag1.7.1的代码
8.4.2 修改控制台代码sentinel-dashboard
修改pom.xml
找到
<artifactId>sentinel-datasource-nacos</artifactId>
将
<scope>test</scope>
删掉
在test目录下
将com.alibaba.csp.sentinel.dashboard.rule.nacos文件夹复制到main对应的目录下
修改
com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfig
文件nacos的地址从配置文件获取,同时将降级,热点,系统,权限转换器注入到spring容器中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class NacosConfig {
private String serverAddr;
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
public Converter<List<AuthorityRuleEntity>, String> authorityRuleEntityEncoder() {
return JSON::toJSONString;
}
public Converter<String, List<AuthorityRuleEntity>> authorityRuleEntityDecoder() {
return s -> JSON.parseArray(s, AuthorityRuleEntity.class);
}
public Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() {
return JSON::toJSONString;
}
public Converter<String, List<DegradeRuleEntity>> degradeRuleEntityDecoder() {
return s -> JSON.parseArray(s, DegradeRuleEntity.class);
}
public Converter<List<ParamFlowRuleEntity>, String> paramFlowRuleEntityEncoder() {
return JSON::toJSONString;
}
public Converter<String, List<ParamFlowRuleEntity>> paramFlowRuleEntityDecoder() {
return s -> JSON.parseArray(s, ParamFlowRuleEntity.class);
}
public Converter<List<SystemRuleEntity>, String> systemRuleEntityEncoder() {
return JSON::toJSONString;
}
public Converter<String, List<SystemRuleEntity>> systemRuleEntityDecoder() {
return s -> JSON.parseArray(s, SystemRuleEntity.class);
}
public ConfigService nacosConfigService() throws Exception {
return ConfigFactory.createConfigService(serverAddr);
}
}约定nacos配置项定义,修改
NacosConfigUtil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public final class NacosConfigUtil {
public static final String GROUP_ID = "SENTINEL_GROUP";
public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules";
public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade-rules";
public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules";
public static final String SYSTEM_DATA_ID_POSTFIX = "-system-rules";
public static final String AUTHORITY_DATA_ID_POSTFIX = "-authority-rules";
public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map";
/**
* cc for `cluster-client`
*/
public static final String CLIENT_CONFIG_DATA_ID_POSTFIX = "-cc-config";
/**
* cs for `cluster-server`
*/
public static final String SERVER_TRANSPORT_CONFIG_DATA_ID_POSTFIX = "-cs-transport-config";
public static final String SERVER_FLOW_CONFIG_DATA_ID_POSTFIX = "-cs-flow-config";
public static final String SERVER_NAMESPACE_SET_DATA_ID_POSTFIX = "-cs-namespace-set";
private NacosConfigUtil() {}
}application.properties
配置文件中添加配置
1 | nacos.server-addr=192.168.80.128:8848 |
8.4.3 流控规则
流控规则官方文档中已经讲了一部分,参照文档进行改造。
修改
com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2
文件bean使用nacos
1
2
3
4
5
6
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;搜索
sidebar.html
文件,找关键字flowV1
,改为flow
1
2
3
4<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>搜索
identity.js
文件,找关键字FlowServiceV1
,改为FlowServiceV2
8.4.4 降级规则
因为官方只有流控规则的改造说明,降级规则需要自己完成,可以参考流控规则FlowControllerV1
到FlowControllerV2
版本的变动,做一下改造。
核心思路:
- 实现
DynamicRulePublisher
(推送规则) 接口,实现publish
方法,用于规则推送配置中心,通过Nacos的configService
的publishConfig
方法实现 - 实现
DynamicRuleProvider
(拉取规则)接口,实现getRules方法,用于规则拉取,通过Nacos的configService
的getConfig
方法实现 - 在Controller中,获取规则的方法中将原来拉去规则的地方改为
dynamicRuleProvider.getRules(app);
方式 - 在保存修改删除规则方法中,同步推送到配置中心
dynamicRulePublisher.publish(app, rules);
代码实现:
推送实现类
DegradeRuleNacosPublisher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DegradeRuleNacosPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> {
private ConfigService configService;
private Converter<List<DegradeRuleEntity>, String> converter;
public void publish(String app, List<DegradeRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, converter.convert(rules));
}
}拉取实现类
DegradeRuleNacosProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DegradeRuleNacosProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> {
private ConfigService configService;
private Converter<String, List<DegradeRuleEntity>> converter;
public List<DegradeRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}DegradeController
1 |
|
8.4.5 热点,系统,权限规则
改造方式和流控规则一样
8.5 改造客户端
- 添加依赖
1 | <dependency> |
- 添加配置信息
1 | spring: |
添加初始化类,用于Sentinel规则持久化DataSource初始化,SentinelNacosDataSourceInit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class SentinelNacosDataSourceInit implements ApplicationRunner {
private SentinelProperties sentinelProperties;
public void run(ApplicationArguments args) {
//获取配置文件中的规则信息
Map<String, DataSourcePropertiesConfiguration> datasource = sentinelProperties.getDatasource();
if(CollectionUtils.isEmpty(datasource)) {
return;
}
datasource.values().forEach(ds -> {
NacosDataSourceProperties nacosDsp = ds.getNacos();
if(nacosDsp!=null) {
//获取规则类型
RuleType ruleType = nacosDsp.getRuleType();
switch (ruleType) {
case FLOW:
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(nacosDsp.getServerAddr(), nacosDsp.getGroupId(), nacosDsp.getDataId(),
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
log.info("Sentinel 流控规则注册完成......");
break;
case DEGRADE:
ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource = new NacosDataSource<>(nacosDsp.getServerAddr(), nacosDsp.getGroupId(), nacosDsp.getDataId(),
source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {
}));
DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
log.info("Sentinel 降级规则注册完成......");
break;
case PARAM_FLOW:
ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleDataSource = new NacosDataSource<>(nacosDsp.getServerAddr(), nacosDsp.getGroupId(), nacosDsp.getDataId(),
source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {
}));
ParamFlowRuleManager.register2Property(paramFlowRuleDataSource.getProperty());
log.info("Sentinel 热点规则注册完成......");
break;
case SYSTEM:
ReadableDataSource<String, List<SystemRule>> systemRuleDataSource = new NacosDataSource<>(nacosDsp.getServerAddr(), nacosDsp.getGroupId(), nacosDsp.getDataId(),
source -> JSON.parseObject(source, new TypeReference<List<SystemRule>>() {
}));
SystemRuleManager.register2Property(systemRuleDataSource.getProperty());
log.info("Sentinel 系统规则注册完成......");
break;
case AUTHORITY:
ReadableDataSource<String, List<AuthorityRule>> authorityRuleDataSource = new NacosDataSource<>(nacosDsp.getServerAddr(), nacosDsp.getGroupId(), nacosDsp.getDataId(),
source -> JSON.parseObject(source, new TypeReference<List<AuthorityRule>>() {
}));
AuthorityRuleManager.register2Property(authorityRuleDataSource.getProperty());
log.info("Sentinel 授权规则注册完成......");
break;
default:
break;
}
}
});
}
}
8.6 验证
- 启动nacos
- 启动客户端
- 启动Sentinel控制台
- 请求客户端的接口,在控制台修改流控配置,查看nacos是否同步更新,测试流控是否生效