sentinel 规则持久化
官网:https://sentinelguard.io/zh-cn/docs/dynamic-rule-configuration.html
规则持久化
流控降级规则加载
# loadRules:从内存中读取,一般用于开发测试
FlowRuleManager.loadRules(List<FlowRule> rules); //读取流控规则
DegradeRuleManager.loadRules(List<DegradeRule> rules); //读取降级规则
# DataSource接口:从文件、数据库、配置中心读取
控制台设置规则后,将规则推送到统一的规则中心(如nacos);
客户端实现ReadableDataSource接口监听规则中心,实时获取规则变更
dataSource接口:控制台将规则数据推送到配置中心,客户端动态读取规则数据
推模式:注册中心向客户端推送规则数据,客户端注册监听器动态获取最新数据,推模式实时性较好,如:zookeeper、nacos、redis、apollo、etcd等
拉模式:客户端定时轮询拉取规则数据,实时性较差,如:RDBMS、文件、consul、euraka等
***************
nacos 数据源
相关依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>x.y.z</version>
</dependency>
注册数据源
@PostConstruct
private static void loadRules() {
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
相关说明
该实现方式是将数据线写入nacos,客户端从nacos中读取数据,
后续nacos中数据如果发生变更,客户端也可以读取到最新的数据;
如果需要通过sentinel-dashboard中设置规则,并将规则推送到nacos中,
需要修改sentinel-dashboard源码,重新编译打包
相关类与接口
NacosDataSource
public class NacosDataSource<T> extends AbstractDataSource<String, T> {
private static final int DEFAULT_TIMEOUT = 3000;
private final ExecutorService pool;
private final Listener configListener;
private final String groupId;
private final String dataId;
private final Properties properties;
private ConfigService configService;
public NacosDataSource(String serverAddr, String groupId, String dataId, Converter<String, T> parser) {
public NacosDataSource(final Properties properties, final String groupId, final String dataId, Converter<String, T> parser) {
AbstractDataSource
public abstract class AbstractDataSource<S, T> implements ReadableDataSource<S, T> {
protected final Converter<S, T> parser;
protected final SentinelProperty<T> property;
public AbstractDataSource(Converter<S, T> parser) {
if (parser == null) {
throw new IllegalArgumentException("parser can't be null");
} else {
this.parser = parser;
this.property = new DynamicSentinelProperty();
}
}
public T loadConfig() throws Exception {
return this.loadConfig(this.readSource());
}
public T loadConfig(S conf) throws Exception {
T value = this.parser.convert(conf);
return value;
}
public SentinelProperty<T> getProperty() {
return this.property;
}
}
ReadableDataSource
public interface ReadableDataSource<S, T> {
T loadConfig() throws Exception;
S readSource() throws Exception;
SentinelProperty<T> getProperty();
void close() throws Exception;
}
Converter
public interface Converter<S, T> {
T convert(S var1);
}
JsonConverter
public class JsonConverter<T> extends SentinelConverter {
public JsonConverter(ObjectMapper objectMapper, Class<T> ruleClass) {
super(objectMapper, ruleClass);
}
}
XmlConverter
public class XmlConverter<T> extends SentinelConverter {
public XmlConverter(XmlMapper xmlMapper, Class<T> ruleClass) {
super(xmlMapper, ruleClass);
}
}
SentinelConverter
public abstract class SentinelConverter<T> implements Converter<String, Collection<Object>> {
private static final Logger log = LoggerFactory.getLogger(SentinelConverter.class);
private final ObjectMapper objectMapper;
private final Class<T> ruleClass;
public SentinelConverter(ObjectMapper objectMapper, Class<T> ruleClass) {
this.objectMapper = objectMapper;
this.ruleClass = ruleClass;
}
public Collection<Object> convert(String source) {
Object ruleCollection;
if (this.ruleClass != FlowRule.class && this.ruleClass != DegradeRule.class && this.ruleClass != SystemRule.class && this.ruleClass != AuthorityRule.class && this.ruleClass != ParamFlowRule.class) {
ruleCollection = new HashSet();
} else {
ruleCollection = new ArrayList();
}
if (StringUtils.isEmpty(source)) {
log.warn("converter can not convert rules because source is empty");
return (Collection)ruleCollection;
} else {
try {
List sourceArray = (List)this.objectMapper.readValue(source, new TypeReference<List<HashMap>>() {
});
Iterator var4 = sourceArray.iterator();
while(var4.hasNext()) {
Object obj = var4.next();
String item = null;
try {
item = this.objectMapper.writeValueAsString(obj);
Optional.ofNullable(this.convertRule(item)).ifPresent((convertRule) -> {
ruleCollection.add(convertRule);
});
} catch (IOException var8) {
log.error("sentinel rule convert error: " + var8.getMessage(), var8);
throw new IllegalArgumentException("sentinel rule convert error: " + var8.getMessage(), var8);
}
}
return (Collection)ruleCollection;
} catch (Exception var9) {
if (var9 instanceof RuntimeException) {
throw (RuntimeException)var9;
} else {
throw new RuntimeException("convert error: " + var9.getMessage(), var9);
}
}
}
}
private Object convertRule(String ruleStr) throws IOException {
return this.objectMapper.readValue(ruleStr, this.ruleClass);
}
}
dashboard 源码修改
说明:只修改了flowRule,其他规则如DegradeRule修改操作类似
修改pom.xml
<!-- 添加注释,去除test属性 -->
<!-- for Nacos rule publisher sample -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!--<scope>test</scope>-->
</dependency>
<!-- 添加lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
application.properties:添加nacos属性
# 自定义类NacosProperties读取属性
nacos.serverAddr=localhost:8848
#nacos.namespace=f94498e5-7384-40fc-a6fc-c7995c66bfd0
# 设置dashboard启动端口默认为8000
server.port=8000
NacosProperties:自定义属性类,读取nacos配置属性
@Data
@Component
@ConfigurationProperties(prefix = "nacos")
public class NacosProperties {
private String serverAddr;
private String groupId = "DEFAULT_GROUP";
private String namespace = "";
}
NacosConstants:定义nacos常量
public class NacosConstants {
public final static String GROUP_ID = "DEFAULT_GROUP";
public final static String FLOW_RULE_DATAID_POSTFIX = "-flow-rule";
}
NacosConfig:创建converter、configService
@Configuration
public class NacosConfig {
@Resource
private NacosProperties nacosProperties;
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityListToJSON(){
return JSON::toJSONString;
}
@Bean
public Converter<String, List<FlowRuleEntity>> jsonToFlowRuleEntityList(){
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
@Bean("configService")
public ConfigService initConfigService() throws NacosException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacosProperties.getServerAddr());
properties.put(PropertyKeyConst.NAMESPACE, nacosProperties.getNamespace());
return ConfigFactory.createConfigService(properties);
}
}
NacosFlowRulePublisher:向nacos中写入FlowRule规则
@Component("nacosFlowRulePublisher")
public class NacosFlowRulePublisher implements DynamicRulePublisher<List<FlowRuleEntity>>{
@Resource
private NacosProperties nacosProperties;
@Resource(name = "configService")
private ConfigService configService;
@Resource
private Converter<List<FlowRuleEntity>, String> converter;
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app不能为空");
if (StringUtils.isEmpty(rules)){
return;
}
String dataId = app + NacosConstants.FLOW_RULE_DATAID_POSTFIX;
configService.publishConfig(dataId, nacosProperties.getGroupId(),
converter.convert(rules), ConfigType.JSON.getType());
}
}
NacosFlowRuleProvider:从nacos中读取flowRule规则
@Component("nacosFlowRuleProvider")
public class NacosFlowRuleProvider implements DynamicRuleProvider<List<FlowRuleEntity>>{
@Resource
private NacosProperties nacosProperties;
@Resource
private ConfigService configService;
@Resource
private Converter<String, List<FlowRuleEntity>> converter;
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
String dataId = appName + NacosConstants.FLOW_RULE_DATAID_POSTFIX;
String rules = configService.getConfig(dataId,nacosProperties.getGroupId(),10000);
if (StringUtils.isEmpty(rules)){
return new ArrayList<>();
}
return converter.convert(rules);
}
}
FlowControllerV2:注入自定义的provider、piublisher
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);
@Autowired
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;
@Autowired
@Qualifier("nacosFlowRuleProvider") //注入自定义的nacosFlowRuleProvider
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("nacosFlowRulePublisher") //注入自定义的nacosFlowRulePublisher
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
sidebar.html:调用v2版本的脚本
<!-- 注释掉该块代码 -->
<!--
<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>
-->
<!-- 调用dashboard.flow -->
<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
flow_v2.html:注释掉代码块
使用示例
application.yml
spring:
application:
name: hello-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
prefix: ${spring.application.name}-flow-rule
sentinel:
transport:
dashboard: localhost:8000
CustomDataSourceConfig:注册nacos数据源,进行规则读写
@Configuration
public class CustomDataSourceConfig {
@Resource
private NacosConfigProperties nacosConfigProperties;
@PostConstruct
public void init(){
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(nacosConfigProperties.getServerAddr(),
nacosConfigProperties.getGroup(), nacosConfigProperties.getPrefix(),
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
}
HelloController
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
***************
使用测试
localhost:8000:sentinel dashboard,定义流控规则
localhost:8848/nacos:nacos查看流控规则
sentinel dashboard修改流控规则
nacos 查看修改后的规则数据
jmeter 限流测试