spring cloud sentinel 动态规则配置
Sentinel 允许在运行时根据不同的需求动态调整限流、熔断等规则。通过动态规则扩展,你可以实现:
- 自动调整:根据业务流量的变化自动调整规则。
- 外部配置支持:规则可以从数据库、配置中心(如 Nacos、Apollo)或者文件等外部来源加载。
- 热更新:不需要重启应用,规则就能实时生效。
Sentinel 提供两种方式修改规则:
- 通过 API 直接修改 (
loadRules
) - 通过
DataSource
适配不同数据源修改
通过 API 修改比较直观,可以通过以下几个 API 修改不同的规则:
FlowRuleManager.loadRules(List<FlowRule> rules); // 修改流控规则
DegradeRuleManager.loadRules(List<DegradeRule> rules); // 修改降级规则
手动修改规则(硬编码方式)一般仅用于测试和演示,生产上一般通过动态规则源的方式来动态管理规则。
DataSource 扩展
DataSource
扩展常见的实现方式有:
- 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件。这样做的方式是简单,缺点是无法及时获取变更;
- 推模式:规则配置中心统一推送,客户端通过注册监听器的方式时刻监听变化,sentinel支持ZooKeeper, Redis, Nacos, Apollo, etcd等配置中心。这种方式有更好的实时性和一致性保证。
上述集中数据源扩展方式,sentinel是支持springboot自动装配的,下面以文件和nacos配置中心的形式来进行演示流量控制规则动态配置。
首先添加数据源扩展依赖
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-extension</artifactId>
</dependency>
文件模式
定义一个规则配置文件FlowRule.json放到resources下
[{"resource": "add","controlBehavior": 0,"count": 3.0,"grade": 1,"limitApp": "default","strategy": 0}
]
一个流量配置规则重要属性如下:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 或线程数模式 | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 | 直接拒绝 |
配置文件配置
spring:cloud:sentinel:transport:port: 8719dashboard: localhost:8080datasource:ds1:file:file: classpath:FlowRule.json #指定规则文件位置data-type: json #指定文件格式rule-type: flow #指定规则类型charset: utf-8 #指定文件编码
sentinel的datasource配置支持是Map<String, DataSourcePropertiesConfiguration>
类型,可以支持同时配置多个数据源。不同的数据源有几个共同的配置项:
data-type: 数据格式类型,默认json
rule-type:规则类型,flow,grade,system等值可配置,具体项可查看RuleType枚举类。
converterClass:配置数据格式化处理类,默认json使用的jackson的ObjectMapper进行解析。
Nacos数据源
nacos添加额外依赖
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
配置文件配置
spring:cloud:sentinel:datasource:ds2:nacos:serverAddr: localhost:8848namespace: sentinelgroupId: flowtestdataId: sentinel_system_flow_rule.jsondataType: jsonruleType: flow
nacos数据源配置和其作为配置中心信息差不多,nacos的连接信息,配置资源文件位置。
手动编码配置
除了使用springboot自动装配扩展数据源,也可以通过手动编码的方式进行自定义配置。使用FlowRuleManager.register2Property()方法进行手动注册数据源,例如手动注册一个文件数据源
//读取配置文件内容
ClassLoader classLoader = getClass().getClassLoader();
String flowRulePath = URLDecoder.decode(classLoader.getResource("FlowRule.json").getFile(), "UTF-8");
//定义converter
Converter<String, List<FlowRule>> flowRuleListParser = s -> JSON.parseObject(s,new TypeReference<List<FlowRule>>(){});
//构造FileRefreshableDataSource
FileRefreshableDataSource<List<FlowRule>> flowRuleDataSource = new FileRefreshableDataSource<>(flowRulePath, flowRuleListParser);
//注册数据源
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
手动注册数据源时候一定要注意代码执行顺序,否则执行过早可能在sentinel的控制台看不到配置规则信息,最好在容器初始化完成后执行,也可以使用sentinel自带的InitFunc的spiloader扩展方式。
动态规则扩展的原理
所有的动态数据源扩展最后都是通过FlowRuleManager.register2Property将数据源注册到规则管理器上。
FlowRuleManager.register2Property()
public static void register2Property(SentinelProperty<List<FlowRule>> property) {AssertUtil.notNull(property, "property cannot be null");synchronized (LISTENER) {RecordLog.info("[FlowRuleManager] Registering new property to flow rule manager");currentProperty.removeListener(LISTENER);property.addListener(LISTENER);currentProperty = property;}}
这里会给property添加一个listenner。这里以文件类型扩展源来看下。
先来看FileRefreshableDataSource内部动态刷新机制,其实很简单就是一个定时器检测文件变化。FileRefreshableDataSource继承抽象类AutoRefreshDataSource,其构造函数会启动定时器。
AutoRefreshDataSource#startTimerService()
private void startTimerService() {//初始化线程池service = Executors.newScheduledThreadPool(1,new NamedThreadFactory("sentinel-datasource-auto-refresh-task", true));service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {try {//文件是否有变化if (!isModified()) {return;}//如果有变化从新从文件读取规则数据T newValue = loadConfig();//调用updateValue方法触发规则更新getProperty().updateValue方法触发规则更新(newValue);} catch (Throwable e) {RecordLog.info("loadConfig exception", e);}}}, recommendRefreshMs, recommendRefreshMs, TimeUnit.MILLISECONDS);}
这里定时周期默认在FileRefreshableDataSource有常量DEFAULT_REFRESH_MS=3000,3秒检测一次。
isModified()在FileRefreshableDataSource实现就是根据文件的修改时间来判断file.lastModified()。
最重要的updateValue()方法,这里的property实例是DynamicSentinelProperty类型,
DynamicSentinelProperty#updateValue()
public boolean updateValue(T newValue) {if (isEqual(value, newValue)) {return false;}RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);value = newValue;for (PropertyListener<T> listener : listeners) {listener.configUpdate(newValue);}return true;}
这里看到会拿出Property中所有的listener依次调用configUpdate方法。listener的设置在我们第一步注册数据源到FlowRuleManager里就设置了。这里listener的类型是FlowPropertyListener。
FlowRuleManager.FlowPropertyListener.configUpdate()
public synchronized void configUpdate(List<FlowRule> value) {Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(value);if (rules != null) {flowRules = rules;}RecordLog.info("[FlowRuleManager] Flow rules received: {}", rules);
}
最后将flowRule更新到内存中。
自动装配数据源原理
在sentinel的自动装配类SentinelAutoConfiguration中会初始化数据源处理类SentinelDataSourceHandler。该handler实现了SmartInitializingSingleton接口,在容器初始化完成后会调用afterSingletonsInstantiated()方法。
SentinelDataSourceHandler#afterSingletonsInstantiated
public void afterSingletonsInstantiated() {sentinelProperties.getDatasource()//循环处理所有配置的datasource.forEach((dataSourceName, dataSourceProperties) -> {try {List<String> validFields = dataSourceProperties.getValidField();if (validFields.size() != 1) {log.error("[Sentinel Starter] DataSource " + dataSourceName+ " multi datasource active and won't loaded: "+ dataSourceProperties.getValidField());return;}AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties.getValidDataSourceProperties();abstractDataSourceProperties.setEnv(env);abstractDataSourceProperties.preCheck(dataSourceName);//将解析验证后数据源信息作为一个bean注册到容器中registerBean(abstractDataSourceProperties, dataSourceName+ "-sentinel-" + validFields.get(0) + "-datasource");}catch (Exception e) {log.error("[Sentinel Starter] DataSource " + dataSourceName+ " build error: " + e.getMessage(), e);}});}
registerBean()方法
private void registerBean(final AbstractDataSourceProperties dataSourceProperties,String dataSourceName) {BeanDefinitionBuilder builder = parseBeanDefinition(dataSourceProperties, dataSourceName);this.beanFactory.registerBeanDefinition(dataSourceName,builder.getBeanDefinition());// init in SpringAbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory.getBean(dataSourceName);// register property in RuleManagerdataSourceProperties.postRegister(newDataSource);}
dataSourceProperties.postRegister()
public void postRegister(AbstractDataSource dataSource) {switch (this.getRuleType()) {case FLOW:FlowRuleManager.register2Property(dataSource.getProperty());break;case DEGRADE:DegradeRuleManager.register2Property(dataSource.getProperty());break;...}}
这里看到最后也是通过FlowRuleManager.register2Property()将数据源注册到规则管理器中。