实现Sentinel持久化到nacos,切断sentinel_dashboard到客户端的通信,直接和nacos通信;
客户端和nacos通信,nacos负责将变化的规则通知到客户端内存。
1、导入依赖
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
2、编写application.yml
server:
port: 8806
spring:
application:
name: sentinel-user-nacos-push #微服务名称
#配置nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
# 添加sentinel的控制台地址
dashboard: 127.0.0.1:8080
# 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
#port: 8719
datasource:
# ds1: #名称自定义,唯一
# nacos:
# server-addr: 127.0.0.1:8848
# dataId: ${spring.application.name}-flow-rules
# groupId: DEFAULT_GROUP
# data-type: json
# rule-type: flow
flow-rules:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}-flow-rules
groupId: DEFAULT_GROUP # 注意groupId对应Sentinel Dashboard中的定义
data-type: json
rule-type: flow
degrade-rules:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}-degrade-rules
groupId: DEFAULT_GROUP
data-type: json
rule-type: degrade
param-flow-rules:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}-param-flow-rules
groupId: DEFAULT_GROUP
data-type: json
rule-type: param-flow
authority-rules:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}-authority-rules
groupId: DEFAULT_GROUP
data-type: json
rule-type: authority
system-rules:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}-system-rules
groupId: DEFAULT_GROUP
data-type: json
rule-type: system
main:
allow-bean-definition-overriding: true
#暴露actuator端点 http://localhost:8800/actuator/sentinel
management:
endpoints:
web:
exposure:
include: '*'
#feign:
# sentinel:
# enabled: true #开启sentinel对feign的支持 默认false
3、在配置中心创建流控规则文件:sentinel-user-nacos-push-flow-rules
[
{
"clusterConfig":{
"acquireRefuseStrategy":0,
"clientOfflineTime":2000,
"fallbackToLocalWhenFail":true,
"resourceTimeout":2000,
"resourceTimeoutStrategy":0,
"sampleCount":10,
"strategy":0,
"thresholdType":0,
"windowIntervalMs":1000
},
"clusterMode":false,
"controlBehavior":0,
"count":1,
"grade":1,
"limitApp":"default",
"maxQueueingTimeMs":500,
"resource":"/test1",
"strategy":0,
"warmUpPeriodSec":10
}
]
启动naocs,sentinel_dashboard和客户端微服务,验证。
此时说明我们能从naocs同步配置。现在我们修改dashboard
出现问题:sentinel_dashboard修改的规则不能同步到nacos。
1、在application.properties里增加nacos的地址:
sentinel.nacos.config.serverAddr=localhost:8848
2、创建包com.alibaba.csp.sentinel.dashboard.rule.nacos.flow,在包下创建FlowRuleNacosProvider和FlowRuleNacosPublisher类。
@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Override
public List<FlowRuleEntity> getRules(String appName,String ip,Integer port) throws NacosException {
// 从Nacos配置中心拉取配置
String rules = configService.getConfig(
appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, NacosConfigUtil.READ_TIMEOUT);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
// 解析json获取到 List
List<FlowRule> list = JSON.parseArray(rules, FlowRule.class);
// FlowRule------->FlowRuleEntity
return list.stream().map(rule ->
FlowRuleEntity.fromFlowRule(appName, ip, port, rule))
.collect(Collectors.toList());
}
}
@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
//发布配置到Nacos配置中心
configService.publishConfig(
app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, NacosConfigUtil.convertToRule(rules));
}
}
其他的规则类似的改造。
3、修改FlowControllerV1类,切断和客户端的通信,直接和nacos通信;
@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (port == null) {
return Result.ofFail(-1, "port can't be null");
}
try {
//List rules = sentinelApiClient.fetchFlowRuleOfMachine(app, ip, port);
//从配置中心获取规则配置
List<FlowRuleEntity> rules = ruleProvider.getRules(app,ip,port);
if (rules != null && !rules.isEmpty()) {
for (FlowRuleEntity entity : rules) {
entity.setApp(app);
if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
entity.setId(entity.getClusterConfig().getFlowId());
}
}
}
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying flow rules", throwable);
return Result.ofThrowable(-1, throwable);
}
}
private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null) {
return Result.ofFail(-1, "port can't be null");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (entity.getGrade() == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (entity.getGrade() != 0 && entity.getGrade() != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
}
if (entity.getCount() == null || entity.getCount() < 0) {
return Result.ofFail(-1, "count should be at lease zero");
}
if (entity.getStrategy() == null) {
return Result.ofFail(-1, "strategy can't be null");
}
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
if (entity.getControlBehavior() == null) {
return Result.ofFail(-1, "controlBehavior can't be null");
}
int controlBehavior = entity.getControlBehavior();
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
if (entity.isClusterMode() && entity.getClusterConfig() == null) {
return Result.ofFail(-1, "cluster config should be valid");
}
return null;
}
@PostMapping("/rule")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
entity.setLimitApp(entity.getLimitApp().trim());
entity.setResource(entity.getResource().trim());
try {
entity = repository.save(entity);
//publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
//发布规则到配置中心
publishRules(entity.getApp());
return Result.ofSuccess(entity);
} catch (Throwable t) {
Throwable e = t instanceof ExecutionException ? t.getCause() : t;
logger.error("Failed to add new flow rule, app={}, ip={}", entity.getApp(), entity.getIp(), e);
return Result.ofFail(-1, e.getMessage());
}
}
@PutMapping("/save.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<FlowRuleEntity> apiUpdateFlowRule(Long id, String app,
String limitApp, String resource, Integer grade,
Double count, Integer strategy, String refResource,
Integer controlBehavior, Integer warmUpPeriodSec,
Integer maxQueueingTimeMs) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
FlowRuleEntity entity = repository.findById(id);
if (entity == null) {
return Result.ofFail(-1, "id " + id + " dose not exist");
}
if (StringUtil.isNotBlank(app)) {
entity.setApp(app.trim());
}
if (StringUtil.isNotBlank(limitApp)) {
entity.setLimitApp(limitApp.trim());
}
if (StringUtil.isNotBlank(resource)) {
entity.setResource(resource.trim());
}
if (grade != null) {
if (grade != 0 && grade != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got");
}
entity.setGrade(grade);
}
if (count != null) {
entity.setCount(count);
}
if (strategy != null) {
if (strategy != 0 && strategy != 1 && strategy != 2) {
return Result.ofFail(-1, "strategy must be in [0, 1, 2], but " + strategy + " got");
}
entity.setStrategy(strategy);
if (strategy != 0) {
if (StringUtil.isBlank(refResource)) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
entity.setRefResource(refResource.trim());
}
}
if (controlBehavior != null) {
if (controlBehavior != 0 && controlBehavior != 1 && controlBehavior != 2) {
return Result.ofFail(-1, "controlBehavior must be in [0, 1, 2], but " + controlBehavior + " got");
}
if (controlBehavior == 1 && warmUpPeriodSec == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && maxQueueingTimeMs == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
entity.setControlBehavior(controlBehavior);
if (warmUpPeriodSec != null) {
entity.setWarmUpPeriodSec(warmUpPeriodSec);
}
if (maxQueueingTimeMs != null) {
entity.setMaxQueueingTimeMs(maxQueueingTimeMs);
}
}
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail: null");
}
//publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
//发布规则到配置中心
publishRules(entity.getApp());
return Result.ofSuccess(entity);
} catch (Throwable t) {
Throwable e = t instanceof ExecutionException ? t.getCause() : t;
logger.error("Error when updating flow rules, app={}, ip={}, ruleId={}", entity.getApp(),
entity.getIp(), id, e);
return Result.ofFail(-1, e.getMessage());
}
}
@DeleteMapping("/delete.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<Long> apiDeleteFlowRule(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
try {
//publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get(5000, TimeUnit.MILLISECONDS);
//发布规则到配置中心
publishRules(oldEntity.getApp());
return Result.ofSuccess(id);
} catch (Throwable t) {
Throwable e = t instanceof ExecutionException ? t.getCause() : t;
logger.error("Error when deleting flow rules, app={}, ip={}, id={}", oldEntity.getApp(),
oldEntity.getIp(), id, e);
return Result.ofFail(-1, e.getMessage());
}
}
private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);
}
/**
* 发布到配置中心
* @param app
* @throws Exception
*/
private void publishRules(/*@NonNull*/ String app) throws Exception {
List<FlowRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
其他的规则类似的改造。
这样sentinel_dashboard就改造完了。启动naocs,sentinel_dashboard和客户端微服务,验证。
nacos配置
到此,就能实现了sentinel_dashboard和nacos的通信。
为什么application.yml中配置nacos的datasource就可以感知配置的变化?
SentinelAutoConfiguration自动配置类里注册了SentinelDataSourceHandler。
@Bean
@ConditionalOnMissingBean
public SentinelDataSourceHandler sentinelDataSourceHandler(
DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties,
Environment env) {
return new SentinelDataSourceHandler(beanFactory, sentinelProperties, env);
}
SentinelDataSourceHandler类实现 SmartInitializingSingleton接口,则在所有的单例bean创建完时执行,它的afterSingletonsInstantiated方法。
@Override
public void afterSingletonsInstantiated() {
sentinelProperties.getDatasource()
.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方法
........省略
// 是NacosDataSourceFactoryBean,所有会调用NacosDataSourceFactoryBean的getObject()
AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory
.getBean(dataSourceName);
// 注册到规格管理器
dataSourceProperties.postRegister(newDataSource);
NacosDataSourceFactoryBean的getObject()
public NacosDataSource getObject() throws Exception {
Properties properties = new Properties();
if(!StringUtils.isEmpty(this.serverAddr)) {
properties.setProperty("serverAddr", this.serverAddr);
} else {
properties.setProperty("accessKey", this.accessKey);
properties.setProperty("secretKey", this.secretKey);
properties.setProperty("endpoint", this.endpoint);
}
if(!StringUtils.isEmpty(this.namespace)) {
properties.setProperty("namespace", this.namespace);
}
if(!StringUtils.isEmpty(this.username)) {
properties.setProperty("username", this.username);
}
if(!StringUtils.isEmpty(this.password)) {
properties.setProperty("password", this.password);
}
//创建NacosDataSource
return new NacosDataSource(properties, this.groupId, this.dataId, this.converter);
}
NacosDataSource构造方法
public NacosDataSource(final Properties properties, final String groupId, final String dataId, Converter<String, T> parser) {
super(parser);
this.pool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(1), new NamedThreadFactory("sentinel-nacos-ds-update"), new DiscardOldestPolicy());
this.configService = null;
if(!StringUtil.isBlank(groupId) && !StringUtil.isBlank(dataId)) {
AssertUtil.notNull(properties, "Nacos properties must not be null, you could put some keys from PropertyKeyConst");
this.groupId = groupId;
this.dataId = dataId;
this.properties = properties;
this.configListener = new Listener() {
public Executor getExecutor() {
return NacosDataSource.this.pool;
}
public void receiveConfigInfo(String configInfo) {
RecordLog.info(String.format("[NacosDataSource] New property value received for (properties: %s) (dataId: %s, groupId: %s): %s", new Object[]{properties, dataId, groupId, configInfo}), new Object[0]);
T newValue = NacosDataSource.this.parser.convert(configInfo);
//配置变化更新内存操作
NacosDataSource.this.getProperty().updateValue(newValue);
}
};
//初始化监听
this.initNacosListener();
//初始化规则配置
this.loadInitialConfig();
} else {
throw new IllegalArgumentException(String.format("Bad argument: groupId=[%s], dataId=[%s]", new Object[]{groupId, dataId}));
}
}
注册到规格管理器
public void postRegister(AbstractDataSource dataSource) {
switch (this.getRuleType()) {
case FLOW:
FlowRuleManager.register2Property(dataSource.getProperty());
break;
case DEGRADE:
DegradeRuleManager.register2Property(dataSource.getProperty());
break;
case PARAM_FLOW:
ParamFlowRuleManager.register2Property(dataSource.getProperty());
break;
case SYSTEM:
SystemRuleManager.register2Property(dataSource.getProperty());
break;
case AUTHORITY:
AuthorityRuleManager.register2Property(dataSource.getProperty());
break;
case GW_FLOW:
GatewayRuleManager.register2Property(dataSource.getProperty());
break;
case GW_API_GROUP:
GatewayApiDefinitionManager.register2Property(dataSource.getProperty());
break;
default:
break;
}
}
到此我们就知道了NacosDataSource是如何创建的,如何保证根据nacos变化更新我们服务的内存。