## sentinel规则持久化 生产环境上,一般都是通过动态规则持久化的方式来动态的管理限流规则,也就是说,很多时候流控规则都会存储在文件,数据库或者配置中心当中,Sentinel的DataSource接口给我们提供了对接任意配置源的能力。所谓的动态规则就是通过控制台配置规则后持久化到各个数据源中。 ![](https://img.kancloud.cn/cf/e8/cfe8227514224d3140b4e4b19cfa24c3_1130x584.png) ## sentinel的两个DataSource接口 * ReadableDataSource:读数据源负责监听持久化的数据源的变更,在接收到变更事件时将最新的数据更新 * WritableDataSource:写数据源负责将变更后的规则写入到持久化的数据源中 ## sentinel-extension模块分析 在 1.7.1 版本,sentinel-extension 模块下的子模块除 sentinel-parameter-flow-control、sentinel-annotation-aspectj 之外,其余子模块都是实现动态数据源的模块。 * sentinel-datasource-extension:定义动态数据源接口、提供抽象类 * sentinel-datasource-redis:基于 Redis 实现的动态数据源 * sentinel-datasource-zookeeper: 基于 ZooKeeper 实现的动态数据源 * 其它省略 sentinel-datasource-extension 模块是 Sentinel 实现动态数据源的核心。 ### SentinelProperty SentinelProperty 是 Sentinel 提供的一个接口,可注册到 Sentinel 提供的各种规则的 Manager,例如 FlowRuleManager,并且可以给 SentinelProperty 添加监听器,在配置改变时,你可以调用 SentinelProperty#updateValue 方法,由它负责调用监听器去更新规则,而不需要调用 FlowRuleManager#loadRules 方法。同时,你也可以注册额外的监听器,在配置改变时做些别的事情。 SentinelProperty 并非 sentinel-datasource-extension 模块中定义的接口,而是 sentinel-core 定义的接口,其源码如下: ~~~ public interface SentinelProperty<T> { void addListener(PropertyListener<T> listener); void removeListener(PropertyListener<T> listener); boolean updateValue(T newValue); } ~~~ * addListener:添加监听器 * removeListener:移除监听器 * updateValue:通知所有监听器配置更新,参数 newValue 为新的配置 默认使用的实现类是 DynamicSentinelProperty,其实现源码如下: ~~~ public class DynamicSentinelProperty<T> implements SentinelProperty<T> { // 存储注册的监听器 protected Set<PropertyListener<T>> listeners = Collections.synchronizedSet(new HashSet<PropertyListener<T>>()); @Override public void addListener(PropertyListener<T> listener) { listeners.add(listener); listener.configLoad(value); } @Override public void removeListener(PropertyListener<T> listener) { listeners.remove(listener); } @Override public boolean updateValue(T newValue) { for (PropertyListener<T> listener : listeners) { listener.configUpdate(newValue); } return true; } } ~~~ DynamicSentinelProperty 使用 Set 存储已注册的监听器,updateValue 负责通知所有监听器,调用监听器的 configUpdate 方法。 ~~~ private static SentinelProperty<List<FlowRule>> currentProperty = new DynamicSentinelProperty<List<FlowRule>>();/** * Load {@link FlowRule}s, former rules will be replaced. * * @param rules new rules to load. */public static void loadRules(List<FlowRule> rules) {    currentProperty.updateValue(rules);} ~~~ 在前面分析 FlowRuleManager 时,我们只关注了其 loadRules 方法,除了使用 loadRules 方法加载规则配置之外,FlowRuleManager 还提供 registerProperty API,用于注册 SentinelProperty。 使用 SentinelProperty 实现加载 FlowRule 的步骤如下: 1. 给 FlowRuleManager 注册一个 SentinelProperty,替换 FlowRuleManager 默认创建的 SentinelProperty(因为默认的 SentinelProperty 外部拿不到); 2. 这一步由 FlowRuleManager 完成,FlowRuleManager 会给 SentinelProperty 注册 FlowPropertyListener 监听器,该监听器负责更新 FlowRuleManager.flowRules 缓存的限流规则; 3. 在应用启动或者规则配置改变时,只需要调用 SentinelProperty#updateValue 方法,由 updateValue 通知 FlowPropertyListener 监听器去更新规则。 FlowRuleManager 支持使用 SentinelProperty 加载或更新限流规则的实现源码如下: ~~~ public class FlowRuleManager { // 缓存限流规则 private static final Map<String, List<FlowRule>> flowRules = new ConcurrentHashMap<String, List<FlowRule>>(); // PropertyListener 监听器 private static final FlowPropertyListener LISTENER = new FlowPropertyListener(); // SentinelProperty private static SentinelProperty<List<FlowRule>> currentProperty // 提供默认的 SentinelProperty = new DynamicSentinelProperty<List<FlowRule>>(); static { // 给默认的 SentinelProperty 注册监听器(FlowPropertyListener) currentProperty.addListener(LISTENER); } // 注册 SentinelProperty public static void register2Property(SentinelProperty<List<FlowRule>> property) { synchronized (LISTENER) { currentProperty.removeListener(LISTENER); // 注册监听器 property.addListener(LISTENER); currentProperty = property; } } } ~~~ 实现更新限流规则缓存的 FlowPropertyListener 是 FlowRuleManager 的一个内部类,其源码如下: ~~~ private static final class FlowPropertyListener implements PropertyListener<List<FlowRule>> { @Override public void configUpdate(List<FlowRule> value) { Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(value); if (rules != null) { // 先清空缓存再写入 flowRules.clear(); flowRules.putAll(rules); } } @Override public void configLoad(List<FlowRule> conf) { Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(conf); if (rules != null) { flowRules.clear(); flowRules.putAll(rules); } } } ~~~ PropertyListener 接口定义的两个方法: * configUpdate:在规则更新时被调用,被调用的时机就是在 SentinelProperty#updateValue 方法被调用时。 * configLoad:在规则首次加载时被调用,是否会被调用由 SentinelProperty 决定。DynamicSentinelProperty 就没有调用这个方法。 所以,现在我们有两种方法更新限流规则: * 调用 FlowRuleManager#loadRules 方法 * 注册 SentinelProperty,调用 SentinelProperty#updateValue 方法 ### ReadableDataSource Sentinel 将读和写数据源抽离成两个接口,一开始只有读接口,写接口是后面才加的功能,目前来看,写接口只在热点参数限流模块中使用到。事实上,使用读接口就已经满足我们的需求。ReadableDataSource 接口的定义如下: ~~~ public interface ReadableDataSource<S, T> { T loadConfig() throws Exception; S readSource() throws Exception; SentinelProperty<T> getProperty(); void close() throws Exception; } ~~~ ReadableDataSource 是一个泛型接口,参数化类型 S 代表用于装载从数据源读取的配置的类型,参数化类型 T 代表对应 Sentinel 中的规则类型。例如,我们可以定义一个 FlowRuleProps 类,用于装载从 yml 配置文件中读取的限流规则配置,然后再将 FlowRuleProps 转为 FlowRule,所以 S 可以替换为 FlowRuleProps,T 可以替换为`List<FlowRule>`。 ReadableDataSource 接口定义的方法解释说明如下: * loadConfig:加载配置。 * readSource:从数据源读取配置,数据源可以是 yaml 配置文件,可以是 MySQL、Redis 等,由实现类决定从哪种数据源读取配置。 * getProperty:获取 SentinelProperty。 * close:用于关闭数据源,例如使用文件存储配置时,可在此方法实现关闭文件输入流等。 如果动态数据源提供 SentinelProperty,则可以调用 getProperty 方法获取动态数据源的 SentinelProperty,将 SentinelProperty 注册给规则管理器(XxxManager),动态数据源在读取到配置时就可以调用自身 SentinelProperty 的 updateValue 方法通知规则管理器(XxxManager)更新规则。 AbstractDataSource 是一个抽象类,该类实现 ReadableDataSource 接口,用于简化具体动态数据源的实现,子类只需要继承 AbstractDataSource 并实现 readSource 方法即可。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"); } this.parser = parser; this.property = new DynamicSentinelProperty<T>(); } @Override public T loadConfig() throws Exception { return loadConfig(readSource()); } public T loadConfig(S conf) throws Exception { T value = parser.convert(conf); return value; } @Override public SentinelProperty<T> getProperty() { return property; } } ~~~ 从源码可以看出: * AbstractDataSource 要求所有子类都必须提供一个数据转换器(Converter),Converter 用于将 S 类型的对象转为 T 类型对象,例如将 FlowRuleProps 对象转为 FlowRule 集合。 * AbstractDataSource 在构造方法中创建 DynamicSentinelProperty,因此子类无需创建 SentinelProperty。 * AbstractDataSource 实现 loadConfig 方法,首先调用子类实现的 readSource 方法从数据源读取配置,返回的对象类型为 S,再调用 Converter#convert 方法,将对象类型由 S 转为 T。 Converter 接口的定义如下: ~~~ public interface Converter<S, T> { T convert(S source); } ~~~ * convert:将类型为 S 的参数 source 转为类型为 T 的对象。 ## sentinel可以支持4种持久化策略 * nacos分布式配置中心 * 阿波罗配置中心 * zookeeper * 本地文件 ![](https://img.kancloud.cn/5a/a1/5aa17a1415deab4ffea710f632e09662_912x138.png) ## 规则流程 ![](https://img.kancloud.cn/b0/7b/b07ba907e33149af08aed8ee1495cf63_1058x503.png) ## 改造内容 ### 推模式 ![](https://img.kancloud.cn/25/2e/252e4902ee18969389a8abb89ed86f8a_991x590.png) * 增加nacos地址 ![](https://img.kancloud.cn/55/9a/559a8ff834f6cb2e74d48acf061621fa_1471x650.png) * 增加配置 ![](https://img.kancloud.cn/41/65/416537d31f9c4163c31b9acab45561a0_1531x579.png) * 增加nacos ![](https://img.kancloud.cn/a8/28/a828349d68f4cb5cb0326306690260e8_506x661.png) * 修改 com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2 ,找到 ![](https://img.kancloud.cn/89/38/89385021f6432d748586bf4973d6157f_1757x679.png) * 注释选中代码 ![](https://img.kancloud.cn/16/1d/161d7a99a15b8627ba9ff4dc4c23aa7e_507x571.png) * 修改流控规则代码 ![](https://img.kancloud.cn/41/9a/419a495682a3aa1c3ea8b36dcc66a6d8_1661x497.png)