Sentinel规则持久化push模式改造
文章目录
- 概述
- 一、push模式的微服务端实现
- 1.1、NacosWritableDataSource
- 1.2、ReadableDataSource
- 二、push模式的Sentinel控制台实现
- 三、规则转换失效的解决
- 总结:Sentinel规则持久化的Push模式实现方案
- 1. 微服务端实现(基于客户端读写数据源)
- 2. Sentinel控制台服务端实现(基于控制台读写)
- 流程对比
- 微服务端模式:
- Sentinel控制台服务端模式:
- 方案对比总结
概述
Sentinel规则持久化有两种模式,一种是前篇中提到的本地存储的pull模式,主要涉及到了FileRefreshableDataSource
和FileWritableDataSource
。前者的目的是为了定时获取配置文件的改动,并且同步到微服务端的内存。 后者的目的则是将sentinel控制台推送的配置文件,写入到本地磁盘文件或数据库/redis等中间件中。因为微服务端的内存,需要通过定时任务拉取本地的变更病同步,所以称之为拉模式
但是这样做,可能存在一定的弊端:
- 如果用户直接对本地文件进行的修改,那么同步至少需要3s的时间间隔。
- 控制台在推送配置文件时,带有ip和端口。如果sentinel是集群部署,那么控制台只会推送配置文件到集群中的一台服务器上,然后将配置文件存在该台服务器的本地文件中。集群之间还需要数据同步。(存储在mysql或redis中可以解决)
除去拉模式外,还有一种整合了Nacos的推模式,大体的思路如下图,将配置中心作为sentinel各种规则的管理者。
- 用户在sentinel控制台进行规则修改,将规则同步到Nacos配置中心。
- 用户在sentinel控制台主动拉取Nacos配置中心的最新配置。
如果需要进行推模式的改造,有微服务端和sentinel服务端的两种改造方案
一、push模式的微服务端实现
**微服务端(客户端)**的改造,是基于sentinel会将规则推送一份到客户端:
1.1、NacosWritableDataSource
客户端 在接收到 服务端推送的配置文件后,除了保存一份到自己的内存外,还需要将该配置信息推送给Nacos配置中心,这里利用到了Nacos的发布配置api,向nacos配置中心写数据:
configService.publishConfig(String dataId, String group, String content, String type)
在客户端加上:
public class NacosWritableDataSource<T> implements WritableDataSource<T> {private final String serverAddr;private final String groupId;private final String dataId;private final Properties properties;private ConfigService configService;private final Converter<T, String> configEncoder;private final Lock lock = new ReentrantLock(true);public NacosWritableDataSource(String serverAddr, String groupId, String dataId, Converter<T, String> configEncoder) {this.serverAddr = serverAddr;this.groupId = groupId;this.dataId = dataId;this.properties = NacosWritableDataSource.buildProperties(serverAddr);this.configEncoder = configEncoder;initConfigService();}private void initConfigService() {try {this.configService = NacosFactory.createConfigService(properties);} catch (NacosException e) {e.printStackTrace();}}static Properties buildProperties(String serverAddr) {Properties properties = new Properties();properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverAddr);return properties;}@Overridepublic void write(T t) throws Exception {lock.lock();try {//向nacos配置中心写数据configService.publishConfig(dataId, groupId, this.configEncoder.convert(t), ConfigType.JSON.getType());} finally {lock.unlock();}}@Overridepublic void close() throws Exception {}
}
可以通过SPI机制实现:
public class NacosDataSourceInitFunc implements InitFunc {@Value("${spring.cloud.sentinel.datasource.param-flow-rules.nacos.server-addr}")private String serverAddr;@Value("${spring.cloud.sentinel.datasource.param-flow-rules.nacos.groupId}")private String groupId;@Value("${spring.cloud.sentinel.datasource.param-flow-rules.nacos.dataId}")private String dataId;@Overridepublic void init() throws Exception {//在com.alibaba.csp.sentinel.command.handler.ModifyRulesCommandHandler.writeToDataSource 中被调用WritableDataSource<List<FlowRule>> writableDataSource = new NacosWritableDataSource<>(serverAddr,groupId,dataId, JSON::toJSONString);WritableDataSourceRegistry.registerFlowDataSource(writableDataSource);}
}
同时需要在配置文件中加入:
spring:application:name: mall-user-sentinel-rule-push-demo #微服务名称#配置nacos注册中心地址cloud:nacos:discovery:server-addr: 127.0.0.1:8848sentinel:transport:# 添加sentinel的控制台地址dashboard: 127.0.0.1:8080# 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer#port: 8719datasource: flow-rules: #名称自定义,唯一nacos:server-addr: 127.0.0.1:8848dataId: ${spring.application.name}-flow-rulesgroupId: SENTINEL_GROUP # 注意groupId对应Sentinel Dashboard中的定义data-type: jsonrule-type: flow
1.2、ReadableDataSource
客户端还需要一个读数据源,负责监听配置文件的变化,并将Nacos的配置文件变更,同步到客户端的内存中,可以通过引入jar包实现:
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
在Nacos的sentinel-extension
,sentinel-datasource-nacos
模块,有一个官方实现好的Nacos读数据源:NacosDataSource
,同样地,也可以通过客户端的SPI机制被调用,在1.1的NacosDataSourceInitFunc
的init
方法中加入:
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource =new NacosDataSource<>(serverAddr, groupId, dataId, source -> {return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {});});FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
在NacosDataSource
中,构造时会做三件事:
- 创建配置监听器,并且实现
receiveConfigInfo
方法,在配置发生变更时回调。 - 初始化配置监听器,将上一步创建的
configListener
和dataId
绑定。 - 加载配置,从本地或Nacos配置中心读取配置。
initNacosListener
:这里最终会调用到ConfigRpcTransportClient#startInternal
,也就是监听listenExecutebell
队列的标志,然后执行executeConfigListen
的逻辑:
- 远程获取配置文件。
- 对有变化的配置调用对应的监听器去处理。
loadInitialConfig
是主动从nacos配置中心拉取配置:
最终使用的是Nacos读取配置的关键api:
configService.getConfig(String dataId, String group, long timeoutMs);
二、push模式的Sentinel控制台实现
除了改造客户端,也可以对于Sentinel的控制台源码进行修改,达到同步配置信息的目的。控制台源码修改的思路,是对于接收客户端页面操作的FlowControllerV1
类进行修改,首先引入ConfigService
,因为需要用到其中的两个关键api:
@Autowiredprivate ConfigService configService;
在新增规则时,原本会调用FlowControllerV1
的apiAddFlowRule
方法,推送配置信息到客户端。
可以修改这一部分的代码,直接将配置推送到Nacos配置中心,而不必再经过客户端:
获取规则也是同样的道理,直接改造成从Nacos配置中心获取:
改服务端源码的方式,比修改客户端代码更加简便,流程上也更加清晰,无需经过第三方去操作Nacos配置中心,而是直接在服务端就对配置中心进行操作。
其中NacosConfigUtil
是自定义的:
public 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 PARAM_FLOW_DATA_ID_POSTFIX = "-param-flow-rules";public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade-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 GATEWAY_FLOW_DATA_ID_POSTFIX = "-gateway-flow-rules";public static final String GATEWAY_API_DATA_ID_POSTFIX = "-gateway-api-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";//超时时间public static final int READ_TIMEOUT = 3000;private NacosConfigUtil() {}/*** RuleEntity----->Rule* @param entities* @return*/public static String convertToRule(List<? extends RuleEntity> entities){return JSON.toJSONString(entities.stream().map(r -> r.toRule()).collect(Collectors.toList()));}/*** ApiDefinitionEntity----->ApiDefinition* @param entities* @return*/public static String convertToApiDefinition(List<? extends ApiDefinitionEntity> entities){return JSON.toJSONString(entities.stream().map(r -> r.toApiDefinition()).collect(Collectors.toList()));}/*** GatewayFlowRuleEntity----->GatewayFlowRule* @param entities* @return*/public static String convertToGatewayFlowRule(List<? extends GatewayFlowRuleEntity> entities){return JSON.toJSONString(entities.stream().map(r -> r.toGatewayFlowRule()).collect(Collectors.toList()));}}
三、规则转换失效的解决
所有的规则实体,都实现了一个共同的父类RuleEntity
。
如果只是在推送规则或获取规则时,将String转成List或将List转成String,例如客户端加入的
NacosWritableDataSource
:
会存在问题,因为每一个RuleEntity
子类实现,层级结构不一定是一样的:
实际上在服务端,每次操作都是经过类型转换的:
服务端同步规则到客户端
服务端从客户端拉取配置
如果是在服务端进行的改造,只需要在FlowControllerV1
中,对于发布规则和拉取规则,进行实体转换即可:
服务端发布规则到配置中心
服务端从配置中心拉取规则
总结:Sentinel规则持久化的Push模式实现方案
在基于 Nacos 的 Sentinel 规则持久化中,Push 模式主要有两种实现思路,分别是基于微服务端和基于Sentinel控制台服务端的实现方式:
1. 微服务端实现(基于客户端读写数据源)
- 读数据源改造:
微服务端通过引入sentinel-datasource-nacos
组件,使用ConfigService.getConfig
API 从 Nacos 配置中心同步读取规则,完成规则的动态感知与内存同步。 - 写数据源改造:
微服务端需自行实现WritableDataSource
接口(例如通过 SPI 或直接注入 Spring 容器),在规则变更时调用ConfigService.publishConfig
API 将规则推送至 Nacos,确保规则持久化和分发。
2. Sentinel控制台服务端实现(基于控制台读写)
- 对
FlowControllerV1
中的接口进行改造,包括:- 查询规则:
apiQueryMachineRules
- 新增规则:
apiAddFlowRule
- 修改规则:
apiUpdateFlowRule
- 删除规则:
apiDeleteFlowRule
- 查询规则:
- 在规则操作时,直接通过 Nacos 的
ConfigService.getConfig
和ConfigService.publishConfig
完成规则的读取与持久化,不再依赖客户端推送,从而简化了规则同步的流程。
流程对比
微服务端模式:
- Sentinel控制台触发规则变更 → 将规则推送至微服务端 → 微服务端通过写数据源将规则持久化到内存并同步推送到 Nacos 配置中心。
- Sentinel控制台读取规则 → 微服务端通过读数据源从 Nacos 获取规则 → 更新本地缓存 → 返回规则给控制台。
Sentinel控制台服务端模式:
- Sentinel控制台触发规则变更 → 直接将变更同步至 Nacos 配置中心 → 微服务端监听 Nacos 配置中心的变更事件,动态更新本地规则内存。
- Sentinel控制台读取规则 → 直接从 Nacos 配置中心拉取规则,同时在本地进行缓存。
方案对比总结
- 基于控制台服务端的实现方式更加简单清晰,避免了客户端冗余处理逻辑。
- 配置同步链路缩短,易于排查问题,且可以充分利用配置中心的高可用能力。
- 对于规则格式转换、兼容性问题的处理也更为集中和统一,降低了微服务端的复杂度。
场景 | 动作 | 关键点 |
---|---|---|
配置中心 → 应用端 | JSON字符串 → Java对象 | 需要 反序列化 |
应用端 → 配置中心 | Java对象 → JSON字符串 | 需要 序列化 |
规则功能类型 | 规则对象类 | 实体封装类(控制台用) | 说明 |
---|---|---|---|
流控规则(Flow Rule) | FlowRule | FlowRuleEntity | 控制QPS或并发量的流量限制 |
熔断降级规则(Degrade Rule) | DegradeRule | DegradeRuleEntity | 根据异常比例、RT或异常数来熔断 |
系统保护规则(System Rule) | SystemRule | SystemRuleEntity | 控制总体 Load、CPU 使用率等 |
授权规则(Authority Rule) | AuthorityRule | AuthorityRuleEntity | 控制资源访问黑白名单 |
热点参数限流规则(ParamFlow Rule) | ParamFlowRule | ParamFlowRuleEntity | 对某些参数的访问频率进行限流 |
网关流控规则(Gateway Flow Rule) | GatewayFlowRule | GatewayFlowRuleEntity | Sentinel网关限流规则(用于Spring Cloud Gateway / Zuul) |
网关API分组规则(Gateway API Definition) | ApiDefinition | GatewayApiDefinitionEntity | 网关端自定义API分组 |