三步实现 Sentinel-Nacos 持久化

一、背景

版本:【Sentinel-1.8.6】

模式:【Push 模式】

参照官网介绍:生产环境下使用Sentinel ,规则管理及推送模式有以下3种模式:

比较之后,目前微服务都使用了各种各样的配置中心,故采用Push 模式来进行持久化,这里也主要介绍集合 Nacos 的持久化。

二、流程结构图

实现思路说明:微服务中增加基于Nacos的写数据源(WritableDataSource),当 Sentinel Dashboard 配置发生变更,则利用 nacos 配置变更通知微服务更新本地缓存。

三、代码整合

1. 在微服务中引入依赖。
xml 复制代码
 <!--sentinel持久化 采用 Nacos 作为规则配置数据源-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
依赖缘由

(ps: 至于为何要引入这个依赖,在此做下展开说明,着急的同学请略过看)

可以看到这个依赖包的源码中只有一个类,那么这个类一定很重要了,看看它具体做了啥:

NacosDataSource 中构造方法如下,里面有个重要的东西: configListener,我们先记住红框框起来的地方。

看看具体的方法 initNacosListener();loadInitialConfig();

java 复制代码
// 初始化 Nacos 监听器
private void initNacosListener() {
	try {
	    // 根据数据源配置信息初始化Nacos 的配置服务: configService 
		this.configService = NacosFactory.createConfigService(this.properties);
		// 添加配置监听器.
		configService.addListener(dataId, groupId, configListener);
	} catch (Exception e) {
		RecordLog.warn("[NacosDataSource] Error occurred when initializing Nacos data source", e);
		e.printStackTrace();
	}
}

// 加载Nacos 已有配置文件
private void loadInitialConfig() {
	try {
		T newValue = loadConfig();
		if (newValue == null) {
			RecordLog.warn("[NacosDataSource] WARN: initial config is null, you may have to check your data source");
		}
		getProperty().updateValue(newValue);
	} catch (Exception ex) {
		RecordLog.warn("[NacosDataSource] Error when loading initial config", ex);
	}
}

又由于 NacosDataSource 继承自 AbstractDataSource,故此处的 loadConfig() 方法是调用 com.alibaba.csp.sentinel.datasource.AbstractDataSource#loadConfig()

java 复制代码
@Override
public T loadConfig() throws Exception {
   return loadConfig(readSource());
}

查看 readSource() 实现:跳转 NacosDataSource.readSource() 中:

java 复制代码
@Override
public String readSource() throws Exception {
	if (configService == null) {
		throw new IllegalStateException("Nacos config service has not been initialized or error occurred");
	}
	// 原来是获取nacos 中的配置
	return configService.getConfig(dataId, groupId, DEFAULT_TIMEOUT);
}

综上查看,Nacos 已经实现了读取配置的代码,那么我们只需要实现写入部分以及相关的配置即可。

2. 自己实现 Nacos 写入代码
1.定义 NacosWritableDataSource

依据读取部分的代码实现,利用 nacoscom.alibaba.nacos.api.config.ConfigService#publishConfig(java.lang.String, java.lang.String, java.lang.String) 方法,既会修改持久化文件,也会同步到对应的微服务。代码如下:

java 复制代码
public class NacosWritableDataSource<T> implements WritableDataSource<T> {

    private NacosDataSourceProperties nacosDataSourceProperties;
    private ConfigService configService;
    private final Converter<T, String> configEncoder;

    private final Lock lock = new ReentrantLock(true);

    public NacosWritableDataSource(NacosDataSourceProperties nacosDataSourceProperties, Converter<T, String> configEncoder) {
        this.nacosDataSourceProperties = nacosDataSourceProperties;
        this.configEncoder = configEncoder;
        // 初始化 Nacos configService
        initConfigService();
    }

    private void initConfigService(){
        try {
            this.configService = NacosFactory.createConfigService(buildProperties(nacosDataSourceProperties));
        } catch (NacosException e) {
            e.printStackTrace();
        }
    }

    private Properties buildProperties(NacosDataSourceProperties nacosDataSourceProperties) {
        Properties properties = new Properties();
        if (!StringUtils.isEmpty(nacosDataSourceProperties.getServerAddr())) {
            properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDataSourceProperties.getServerAddr());
        } else {
            properties.setProperty(PropertyKeyConst.ACCESS_KEY, nacosDataSourceProperties.getAccessKey());
            properties.setProperty(PropertyKeyConst.SECRET_KEY, nacosDataSourceProperties.getSecretKey());
            properties.setProperty(PropertyKeyConst.ENDPOINT, nacosDataSourceProperties.getEndpoint());
        }
        if (!StringUtils.isEmpty(nacosDataSourceProperties.getNamespace())) {
            properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDataSourceProperties.getNamespace());
        }
        if (!StringUtils.isEmpty(nacosDataSourceProperties.getUsername())) {
            properties.setProperty(PropertyKeyConst.USERNAME, nacosDataSourceProperties.getUsername());
        }
        if (!StringUtils.isEmpty(nacosDataSourceProperties.getPassword())) {
            properties.setProperty(PropertyKeyConst.PASSWORD, nacosDataSourceProperties.getPassword());
        }
        return properties;
    }

    @Override
    public void write(T value) throws Exception {
        lock.lock();
        try {
           // 发布新配置
            configService.publishConfig(nacosDataSourceProperties.getDataId(), nacosDataSourceProperties.getGroupId(), this.configEncoder.convert(value), ConfigType.JSON.getType());
        } catch (Exception e) {
            throw e;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void close() throws Exception {

    }


}

上述只实现了数据配置更新与同步,我们还需要根据 Sentinel 的不用规则来区分具体是更新哪个文件,因此有下:

2.定义 SentinelNacosDataSourceHandler
java 复制代码
public class SentinelNacosDataSourceHandler implements SmartInitializingSingleton {

    private final SentinelProperties sentinelProperties;

    public SentinelNacosDataSourceHandler(SentinelProperties sentinelProperties) {
        this.sentinelProperties = sentinelProperties;
    }
  //实现SmartInitializingSingleton 的接口后,当所有非懒加载的单例Bean 都初始化完成以后,Spring 的IOC 容器会调用该接口的 afterSingletonsInstantiated() 方法
    @Override
    public void afterSingletonsInstantiated() {
        sentinelProperties.getDatasource().values().forEach(this::registryWriter);
    }

    private void registryWriter(DataSourcePropertiesConfiguration dataSourceProperties) {
        final NacosDataSourceProperties nacosDataSourceProperties = dataSourceProperties.getNacos();
        if (nacosDataSourceProperties == null) {
            return;
        }
        final RuleType ruleType = nacosDataSourceProperties.getRuleType();
        // 通过数据源配置的 ruleType 来注册数据源
        switch (ruleType) {
            case FLOW:
                WritableDataSource<List<FlowRule>> flowRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                WritableDataSourceRegistry.registerFlowDataSource(flowRuleWriter);
                break;
            case DEGRADE:
                WritableDataSource<List<DegradeRule>> degradeRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWriter);
                break;
            case PARAM_FLOW:
                WritableDataSource<List<ParamFlowRule>> paramFlowRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWriter);
                break;
            case SYSTEM:
                WritableDataSource<List<SystemRule>> systemRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                WritableDataSourceRegistry.registerSystemDataSource(systemRuleWriter);
                break;
            case AUTHORITY:
                WritableDataSource<List<AuthorityRule>> authRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                WritableDataSourceRegistry.registerAuthorityDataSource(authRuleWriter);
                break;
            default:
                break;
        }
    }
}
3. Spring IOC 管理 SentinelNacosDataSourceHandler
java 复制代码
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(SentinelAutoConfiguration.class)
public class SentinelNacosDataSourceConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SentinelNacosDataSourceHandler sentinelNacosDataSourceHandler(SentinelProperties sentinelProperties){
        return new SentinelNacosDataSourceHandler(sentinelProperties);
    }

}
4. 配置文件

bootstrap.yml 中的配置文件内容如下:

yml 复制代码
spring:
  application:
    name: spring-cloud-sentinel-demo  #微服务名称
  cloud:
    nacos:
      config:  #配置nacos配置中心地址
        server-addr: 192.168.0.123:8847
        username: nacos
        password: nacos
        file-extension: yml   # 指定配置文件的扩展名为yml
        namespace: 6a7dbc5f-b376-41c6-a282-74ad4fd4829b

nacos 中微服务对应的配置文件内容如下:(也就是 application.yml ):

yml 复制代码
server:
  port: 8800

spring:
  application:
    name: spring-cloud-sentinel-demo  #微服务名称

  cloud:
    nacos:  #配置nacos注册中心地址
      discovery:
        server-addr: 192.168.0.123:8847
        username: nacos
        password: nacos
		
    sentinel:
      transport:
        dashboard: 192.168.0.123:8091
      datasource:
        flow-rules: #流控规则
          nacos:
            server-addr: 192.168.0.123:8847
            namespace: 6a7dbc5f-b376-41c6-a282-74ad4fd4829b
            username: nacos
            password: nacos
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP   # 注意groupId对应Sentinel Dashboard中的定义
            data-type: json
            rule-type: flow
        degrade-rules: #降级规则
          nacos:
            server-addr: 192.168.0.123:8847
            namespace: 6a7dbc5f-b376-41c6-a282-74ad4fd4829b
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: degrade
        param-flow-rules:
          nacos:
            server-addr: 192.168.0.123:8847
            namespace: 6a7dbc5f-b376-41c6-a282-74ad4fd4829b
            dataId: ${spring.application.name}-param-flow-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: param-flow
        authority-rules:
          nacos:
            server-addr: 192.168.0.123:8847
            namespace: 6a7dbc5f-b376-41c6-a282-74ad4fd4829b
            dataId: ${spring.application.name}-authority-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: authority
        system-rules:
          nacos:
            server-addr: 192.168.0.123:8847
            namespace: 6a7dbc5f-b376-41c6-a282-74ad4fd4829b
            dataId: ${spring.application.name}-system-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: system
3. 测试
1. 在 Sentinel Dashboard 新建流控规则:

查看 Nacos 配置中心,在对应的 namespace 下多了一个配置文件:spring-cloud-sentinel-demo-flow-rules 因为在上述文件中指定了 flow-rules 数据源的 namespacegroup
查看此配置文件的详情可以看到:内容就是刚才流控规则的序列化内容:

综上,Sentinel Dashboard 新建的规则可以成功序列化到 Nacos 的配置中。

2. 在 Nacos 控制台修改对应的 count,在Sentinel Dashboard 查看是否同步显示:

修改前先查看Sentinel Dashboard 目前流控的阈值为2:

修改Nacos 中 spring-cloud-sentinel-demo-flow-rules 的配置,并发布:

修改后查看 Sentinel Dashboard 目前流控的阈值为3:

测试流控效果,已可以正常限流:

4. 其他规则也类似于流控规则

四、总结

最后附上这部分源码梳理图,可以结合上述内容一起理解。

相关推荐
东阳马生架构3 天前
Nacos源码—9.Nacos升级gRPC分析四
nacos
RingWu3 天前
微服务架构-限流、熔断:Alibaba Sentinel入门
微服务·架构·sentinel
东阳马生架构4 天前
Nacos源码—8.Nacos升级gRPC分析三
nacos
东阳马生架构6 天前
Nacos源码—7.Nacos升级gRPC分析四
nacos·注册中心·配置中心
东阳马生架构6 天前
Nacos源码—7.Nacos升级gRPC分析三
nacos·注册中心·配置中心
东阳马生架构7 天前
Nacos源码—7.Nacos升级gRPC分析二
nacos
东阳马生架构8 天前
Nacos源码—6.Nacos升级gRPC分析一
nacos
gxh19928 天前
springboot微服务连接nacos超时
linux·后端·微服务·nacos
东阳马生架构8 天前
Nacos源码—5.Nacos配置中心实现分析二
nacos·注册中心·配置中心
东阳马生架构8 天前
Nacos源码—5.Nacos配置中心实现分析一
nacos·注册中心·配置中心