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配置中心的形式来进行演示流量控制规则动态配置。
首先添加数据源扩展依赖
java
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</dependency>
文件模式
定义一个规则配置文件FlowRule.json放到resources下
json
[
{
"resource": "add",
"controlBehavior": 0,
"count": 3.0,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]
一个流量配置规则重要属性如下:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 或线程数模式 | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 | 直接拒绝 |
配置文件配置
yaml
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080
datasource:
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>
配置文件配置
yaml
spring:
cloud:
sentinel:
datasource:
ds2:
nacos:
serverAddr: localhost:8848
namespace: sentinel
groupId: flowtest
dataId: sentinel_system_flow_rule.json
dataType: json
ruleType: flow
nacos数据源配置和其作为配置中心信息差不多,nacos的连接信息,配置资源文件位置。
手动编码配置
除了使用springboot自动装配扩展数据源,也可以通过手动编码的方式进行自定义配置。使用FlowRuleManager.register2Property()方法进行手动注册数据源,例如手动注册一个文件数据源
java
//读取配置文件内容
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()
java
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()
java
private void startTimerService() {
//初始化线程池
service = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("sentinel-datasource-auto-refresh-task", true));
service.scheduleAtFixedRate(new Runnable() {
@Override
public 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()
java
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()
java
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
java
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()方法
java
private void registerBean(final AbstractDataSourceProperties dataSourceProperties,
String dataSourceName) {
BeanDefinitionBuilder builder = parseBeanDefinition(dataSourceProperties, dataSourceName);
this.beanFactory.registerBeanDefinition(dataSourceName,
builder.getBeanDefinition());
// init in Spring
AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory
.getBean(dataSourceName);
// register property in RuleManager
dataSourceProperties.postRegister(newDataSource);
}
dataSourceProperties.postRegister()
java
public void postRegister(AbstractDataSource dataSource) {
switch (this.getRuleType()) {
case FLOW:
FlowRuleManager.register2Property(dataSource.getProperty());
break;
case DEGRADE:
DegradeRuleManager.register2Property(dataSource.getProperty());
break;
...
}
}
这里看到最后也是通过FlowRuleManager.register2Property()将数据源注册到规则管理器中。