Nacos 配置中心基本使用(十六)

基于dataid为yaml的文件扩展配置

spring-cloud-starter-alibaba-nacos-confifig默认支持的文件格式是properties, 如果我们想用其他格式的文件,可以只需要完成以下两步:
1、在应用的 bootstrap.properties 配置文件中显示的声明 dataid 文件扩展名。如下所示bootstrap.properties
spring.cloud.nacos.config.file-extension=yaml
2、在Nacos控制台,修改配置文件的类型,改成yml

针对profifile粒度配置

spring-cloud-starter-alibaba-nacos-confifig 在加载配置的时候,不仅仅加载了以 dataid 为${spring.application.name}.${file-extension:properties} 为前缀的基础配置,还加载了dataid为 ${spring.application.name}-${profile}.${file-extension:properties} 的基础配置。在日常开发中如果遇到多套环境下的不同配置,可以通过Spring 提供的${spring.profiles.active} 这个配置项来配置。
  • 在bootstrap.properties中添加profifile
spring.profiles.active=develop
  • Nacos 上新增一个dataid为:nacos-confifig-develop.yaml的基础配置,如下所示:
Data ID: nacos-dubbo-provider-develop.yaml 
Group : DEFAULT_GROUP 
配置格式: YAML 
配置内容: current.env: develop-env
如果需要切换到生产环境,只需要更改 ${spring.profiles.active} 参数配置即可。如下所示:
spring.profiles.active=product

Nacos 中的Namespace和Group

在nacos中提供了namespace和group命名空间和分组的机制。,它是Nacos提供的一种数据模型,也就是我们要去定位到一个配置,需要基于namespace- > group ->dataid来实现。namespace可以解决多环境以及多租户数据的隔离问题。比如在多套环境下,可以根据指定环境创建不同的namespace,实现多环境隔离。或者在多租户的场景中,每个用户可以维护自己的namespace,实现每个用户的配置数据和注册数据的隔离。group是分组机制,它的纬度是实现服务注册信息或者DataId的分组管理机制,对于group的用法,没有固定的规则,它也可以实现不同环境下的分组,也可以实现同一个应用下不同配置类型或者不同业务类型的分组。
 
官方建议是,namespace用来区分不同环境,group可以专注在业务层面的数据分组。实际上在使用过程中,最重要的是提前定要统一的口径和规定,避免不同的项目团队混用导致后期维护混乱的问题。

自定义namespace

在没有明确指定 ${spring.cloud.nacos.config.namespace} 配置的情况下, 默认使用的是 Nacos上 Public 这个namespae。如果需要使用自定义的命名空间,可以通过以下配置来实现:
 
spring.cloud.nacos.config.namespace=b3404bc0-d7dc-4855-b519-570ed34b62d7
该配置必须放在 bootstrap.properties 文件中。此外spring.cloud.nacos.config.namespace 的值是 namespace 对应的 id,id 值可以在 Nacos的控制台获取。
并且在添加配置时注意不要选择其他的 namespae,否则将会导致读取不到正确的配置。

自定义group

在没有明确指定 ${spring.cloud.nacos.config.group} 配置的情况下, 默认使用的是DEFAULT_GROUP 。如果需要自定义自己的 Group,可以通过以下配置来实现:
spring.cloud.nacos.config.group=DEVELOP_GROUP
该配置必须放在 bootstrap.properties 文件中。并且在添加配置时 Group 的值一定要和spring.cloud.nacos.config.group 的配置值一致

自定义扩展的DataId

Spring Cloud Alibaba Nacos Confifig 从 0.2.1 版本后,可支持自定义 Data Id 的配置。关于这部分详细的设计可参考这里。 一个完整的配置案例如下所示:
spring.application.name=opensource-service-provider 
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

# config external configuration
# 1、Data Id 在默认的组 DEFAULT_GROUP,不支持配置的动态刷新
spring.cloud.nacos.config.extension-configs[0].data-id=ext-config-common01.properties
# 2、Data Id 不在默认的组,不支持动态刷新
spring.cloud.nacos.config.extension-configs[1].data-id=ext-config-common02.properties
spring.cloud.nacos.config.extension-configs[1].group=GLOBALE_GROUP
# 3、Data Id 既不在默认的组,也支持动态刷新
spring.cloud.nacos.config.extension-configs[2].data-id=ext-config-common03.properties
spring.cloud.nacos.config.extension-configs[2].group=REFRESH_GROUP
spring.cloud.nacos.config.extension-configs[2].refresh=true
可以看到:
  • 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的配置方式来支持多个Data Id 的配置。
  • 通过 spring.cloud.nacos.config.extension-configs[n].group 的配置方式自定义 Data Id所在的组,不明确配置的话,默认是 DEFAULT_GROUP。
  • 通过 spring.cloud.nacos.config.extension-configs[n].refresh 的配置方式来控制该Data Id 在配置变更时,是否支持应用中可动态刷新, 感知到最新的配置值。默认是不支持的。
多个 Data Id 同时配置时,他的优先级关系是 spring.cloud.nacos.config.extension-configs[n].data-id 其中 n 的值越大,优先级越高。
Note spring.cloud.nacos.config.extension-configs[n].data-id 的值必须带文件扩展名,文件扩展名既可支持 properties,又可以支持 yaml/yml。 此时spring.cloud.nacos.config.file-extension 的配置对自定义扩展配置的 Data Id 文件扩展名没有影响;通过自定义扩展的 Data Id 配置,既可以解决多个应用间配置共享的问题,又可以支持一个应用有多个配置文件。为了更加清晰的在多个应用间配置共享的 Data Id ,你可以通过以下的方式来配置:通过自定义扩展的 Data Id 配置,既可以解决多个应用间配置共享的问题,又可以支持一个应用有多个配置文件。为了更加清晰的在多个应用间配置共享的 Data Id ,你可以通过以下的方式来配置:
 
# 配置支持共享的 Data Id 
spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml
# 配置 Data Id 所在分组,缺省默认 DEFAULT_GROUP
spring.cloud.nacos.config.shared-configs[0].group=GROUP_APP1
# 配置Data Id 在配置变更时,是否动态刷新,缺省默认 false
spring.cloud.nacos.config.shared-configs[0].refresh=true
可以看到:
  • 通过 spring.cloud.nacos.config.shared-configs[n].data-id 来支持多个共享 Data Id 的配置。
  • 通过 spring.cloud.nacos.config.shared-configs[n].group 来配置自定义 Data Id 所在的组,不明确配置的话,默认是 DEFAULT_GROUP。
  • 通过 spring.cloud.nacos.config.shared-configs[n].refresh 来控制该Data Id在配置变更时,是否支持应用中动态刷新,默认false。

配置的优先级

Spring Cloud Alibaba Nacos Confifig 目前提供了三种配置能力从 Nacos 拉取相关的配置。
A: 通过 spring.cloud.nacos.config.shared-configs[n].data-id 支持多个共享 Data Id 的配置
B: 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的方式支持多个扩展Data Id 的配置
C: 通过内部相关规则(应用名、应用名+ Profifile )自动生成相关的 Data Id 配置当三种方式共同使用时,他们的一个优先级关系是:A < B < C

配置动态刷新

配置的动态刷新,仅需要使用@RefreshScope注解即可。

使用配置类来创建bean时,若要实现注入bean的刷新,需要在配置类和Bean创建方法上均加上@RefreshScope注解。在对应配置被修改后,所有开启了刷新的注入bean在下一次调用时会重新进行初始化并替换掉之前注入的Bean。因bean替换时,弃用的bean会执行销毁方法来释放资源,故自定义Bean建议实现Closeable接口。

需要注意的是,由于@RefreshScope注解底层是使用cglib动态代理来实现,而cglib是创建动态子类继承来完成功能的增强,在使用@RefreshScope注解刷新包含final属性/final方法的bean时,会导致返回的代理对象为null的情况。典型的例子比如Elasticsearch的RestHighLevelClient。此时需要将需要刷新的Bean封装一层,避免final属性/final方法的问题。

下面是常用的数据源类型bean的动态刷新配置代码:

Nacos配置
mybatis:
  datasource:
    enable: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    minIdle: 5
    validationQuery: select version()
    keepAlive: true
    initialSize: 5
    maxWait: 60000
    poolPreparedStatements: true
    filters: stat,config
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: org.postgresql.Driver
    url: jdbc:postgresql://127.0.0.1:5432/postgres
    username: xxx
    password: xxx
    maxPoolPreparedStatementPerConnectionSize: 20
    testOnBorrow: true
    testWhileIdle: true
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    testOnReturn: false
    maxActive: 30
    validationQueryTimeout: 10
    
jedispool:
  config:
    enable: true
    host: 127.0.0.1
    port: 6379
    password:
    timeOut: 30000
    maxIdle: 500
    maxWaitMillis: 50000
    maxTotal: 10000
 
es:
  config:
    enable: true
    ip: 127.0.0.1
    port: 9200

基于Jedis的redis数据源

配置类

package cn.com.geostar.datasource;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
 
/**
 * @author xiawei
 * @date 2020/8/10 11:33
 */
 
@Component
@RefreshScope
@ConfigurationProperties(prefix = "jedispool.config")
public class JedisPoolConfigure {
    private boolean enable;
    private String host;
    private Integer port;
    private String password;
    private Integer timeOut;
    private Integer maxIdle;
    private Integer maxWaitMillis;
    private Integer maxTotal;
 
 // getter setter
}

Bean创建

 /**
     * jedis连接池,开启刷新
     */
    @Bean
    @RefreshScope
    public JedisPool jedisPool() {
        if (!poolConfig.isEnable()) {
            logger.warn("redis datasource is disable !");
            return null;
        }
 
        String host = poolConfig.getHost();
        Integer port = poolConfig.getPort();
 
        if (StringUtils.isBlank(host) || port == null) {
            logger.warn("redis datasource config: host/port must be specified!");
            return null;
        }
 
        int timeOut = Optional.ofNullable(poolConfig.getTimeOut()).orElseGet(() -> {
            logger.info("redis datasource config: timeOut not be specified, used default value 30000 .");
            return 30000;
        });
 
        Integer maxTotal = Optional.ofNullable(poolConfig.getMaxTotal()).orElseGet(() -> {
            logger.info("redis datasource config: maxTotal not be specified, used default value 10000 .");
            return 10000;
        });
        Integer maxIdle = Optional.ofNullable(poolConfig.getMaxIdle()).orElseGet(() -> {
            logger.info("redis datasource config: maxIdle not be specified, used default value 500 .");
            return 500;
        });
        Integer maxWaitMillis = Optional.ofNullable(poolConfig.getTimeOut()).orElseGet(() -> {
            logger.info("redis datasource config: maxWaitMillis not be specified, used default value 50000 .");
            return 50000;
        });
        String password = poolConfig.getPassword();
 
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMaxWaitMillis(maxWaitMillis);
        if (StringUtils.isBlank(password)) {
            logger.info("redis datasource init successful, host = " + host + ", port = " + port);
            return new JedisPool(config, host, port, timeOut);
        } else {
            logger.info("redis datasource init successful, host = " + host + ", port = " + port);
            return new JedisPool(config, host, port, timeOut, password);
        }
    }

基于官方RestHighLevelClient的ES数据源

Client封装类

package cn.com.geostar.datasource;
 
import org.elasticsearch.client.RestHighLevelClient;
 
import java.io.Closeable;
import java.io.IOException;
 
/**
 * @author xiawei
 * @date 2020/8/11 9:29
 */
public class ElasticSearchRestClient implements Closeable {
    private RestHighLevelClient restHighLevelClient;
 
    public RestHighLevelClient getRestHighLevelClient() {
        return restHighLevelClient;
    }
 
    public void setRestHighLevelClient(RestHighLevelClient restHighLevelClient) {
        this.restHighLevelClient = restHighLevelClient;
    }
 
    @Override
    public void close() throws IOException {
        restHighLevelClient.close();
    }
}

配置类

package cn.com.geostar.datasource;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
 
/**
 * @author xiawei
 * @date 2020/8/10 14:26
 */
@Component
@RefreshScope
@ConfigurationProperties(prefix = "es.config")
public class ElasticSearchConfigure {
    private String ip;
    private Integer port;
    private boolean enable;
 
    // getter setter
}

Bean创建

 /**
     * es初始化,开启刷新
     */
    @Bean
    @RefreshScope
    public ElasticSearchRestClient restHighLevelClient() {
        String ip = elasticSearchConfig.getIp();
        Integer port = elasticSearchConfig.getPort();
        if (!elasticSearchConfig.isEnable()) {
            logger.warn("elasticsearch datasource is disable!");
            return null;
        }
        if (StringUtils.isBlank(ip) || port == null) {
            logger.warn("elasticsearch datasource config: ip/port must be specified!");
            return null;
        }
        RestClientBuilder builder = RestClient.builder(new HttpHost(ip, port, "http"));
        RestHighLevelClient client = new RestHighLevelClient(builder);
        ElasticSearchRestClient esrc = new ElasticSearchRestClient();
        esrc.setRestHighLevelClient(client);
        logger.info("elasticsearch datasource init successful, ip = " + ip + ", port = " + port);
        return esrc;
    }

基于Druid连接池和Mybatis持久层框架的关系数据库

DataSource配置类

package cn.com.geostar.datasource;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
 
/**
 * @author xiawei
 * @date 2020/8/11 11:25
 */
@Component
@RefreshScope
@ConfigurationProperties(prefix = "mybatis.datasource")
public class MybatisDataSourceConfig {
    private boolean enable;
    private String connectionProperties;
    private Integer minIdle;
    private String validationQuery;
    private Boolean keepAlive;
    private Integer initialSize;
    private Integer maxWait;
    private Boolean poolPreparedStatements;
    private String filters;
    private String type;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private Integer maxPoolPreparedStatementPerConnectionSize;
    private Boolean testOnBorrow;
    private Boolean testWhileIdle;
    private Integer timeBetweenEvictionRunsMillis;
    private Integer minEvictableIdleTimeMillis;
    private Boolean testOnReturn;
    private Integer maxActive;
    private Integer validationQueryTimeout;
 
   // getter setter
}

Bean创建

 @Bean
    @RefreshScope
    public DataSource dataSource() throws SQLException {
        if (!mybatisDataSourceConfig.isEnable()) {
            logger.warn("mybatis datasource is disable !");
            return null;
        }
        String validationQuery = mybatisDataSourceConfig.getValidationQuery();
        String filters = mybatisDataSourceConfig.getFilters();
        String driverClassName = mybatisDataSourceConfig.getDriverClassName();
        String url = mybatisDataSourceConfig.getUrl();
        String username = mybatisDataSourceConfig.getUsername();
        String password = mybatisDataSourceConfig.getPassword();
 
        if (StringUtils.isBlank(validationQuery) || StringUtils.isBlank(filters) || StringUtils.isBlank(driverClassName)
                || StringUtils.isBlank(url) || StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            logger.warn("mybatis datasource config: validationQuery、filters、driverClassName、url、username、password must be specified!");
            return null;
        }
 
        DruidDataSource druidDataSource = checkDruidDataSourceParams(mybatisDataSourceConfig);
        logger.info("druidDataSource init successful, url = " + url);
        return druidDataSource;
    }
 
    private DruidDataSource checkDruidDataSourceParams(MybatisDataSourceConfig mybatisDataSourceConfig) throws SQLException {
        String connectionProperties = Optional.ofNullable(mybatisDataSourceConfig.getConnectionProperties())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: connectionProperties not be specified," +
                            " used default value {druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000} .");
                    return "druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000";
                });
        Integer minIdle = Optional.ofNullable(mybatisDataSourceConfig.getMinIdle())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: minIdle not be specified, used default value {5} .");
                    return 5;
                });
        Boolean keepAlive = Optional.ofNullable(mybatisDataSourceConfig.getKeepAlive())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: keepAlive not be specified, used default value {true} .");
                    return true;
                });
        Integer initialSize = Optional.ofNullable(mybatisDataSourceConfig.getInitialSize())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: initialSize not be specified, used default value {5} .");
                    return 5;
                });
        Integer maxWait = Optional.ofNullable(mybatisDataSourceConfig.getMaxWait())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: maxWait not be specified, used default value {60000} .");
                    return 60000;
                });
        Boolean poolPreparedStatements = Optional.ofNullable(mybatisDataSourceConfig.getPoolPreparedStatements())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: poolPreparedStatements not be specified, used default value {true} .");
                    return true;
                });
        Integer maxPoolPreparedStatementPerConnectionSize = Optional.ofNullable(mybatisDataSourceConfig.getMaxPoolPreparedStatementPerConnectionSize())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: maxPoolPreparedStatementPerConnectionSize not be specified, used default value {20} .");
                    return 20;
                });
        Boolean testOnBorrow = Optional.ofNullable(mybatisDataSourceConfig.getTestOnBorrow())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: testOnBorrow not be specified, used default value {true} .");
                    return true;
                });
        Boolean testWhileIdle = Optional.ofNullable(mybatisDataSourceConfig.getTestWhileIdle())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: testWhileIdle not be specified, used default value {true} .");
                    return true;
                });
        Integer timeBetweenEvictionRunsMillis = Optional.ofNullable(mybatisDataSourceConfig.getTimeBetweenEvictionRunsMillis())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: timeBetweenEvictionRunsMillis not be specified, used default value {60000} .");
                    return 60000;
                });
        Integer minEvictableIdleTimeMillis = Optional.ofNullable(mybatisDataSourceConfig.getMinEvictableIdleTimeMillis())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: minEvictableIdleTimeMillis not be specified, used default value {300000} .");
                    return 300000;
                });
        Boolean testOnReturn = Optional.ofNullable(mybatisDataSourceConfig.getTestOnReturn())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: testOnReturn not be specified, used default value {false} .");
                    return false;
                });
        Integer maxActive = Optional.ofNullable(mybatisDataSourceConfig.getMaxActive())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: maxActive not be specified, used default value {30} .");
                    return 30;
                });
        Integer validationQueryTimeout = Optional.ofNullable(mybatisDataSourceConfig.getValidationQueryTimeout())
                .orElseGet(() -> {
                    logger.info("mybatis datasource config: validationQueryTimeout not be specified, used default value {10} .");
                    return 10;
                });
 
        String validationQuery = mybatisDataSourceConfig.getValidationQuery();
        String filters = mybatisDataSourceConfig.getFilters();
        String driverClassName = mybatisDataSourceConfig.getDriverClassName();
        String url = mybatisDataSourceConfig.getUrl();
        String username = mybatisDataSourceConfig.getUsername();
        String password = mybatisDataSourceConfig.getPassword();
 
        DruidDataSource build = DataSourceBuilder.create().type(DruidDataSource.class)
                .build();
        build.setConnectionProperties(connectionProperties);
        build.setMinIdle(minIdle);
        build.setValidationQuery(validationQuery);
        build.setKeepAlive(keepAlive);
        build.setInitialSize(initialSize);
        build.setMaxWait(maxWait);
        build.setPoolPreparedStatements(poolPreparedStatements);
        build.setFilters(filters);
        build.setDriverClassName(driverClassName);
        build.setUrl(url);
        build.setUsername(username);
        build.setPassword(password);
        build.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        build.setTestOnBorrow(testOnBorrow);
        build.setTestWhileIdle(testWhileIdle);
        build.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        build.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        build.setTestOnReturn(testOnReturn);
        build.setMaxActive(maxActive);
        build.setValidationQueryTimeout(validationQueryTimeout);
        return build;
    }

SqlSessionFactory和TransactionManager

package cn.com.geostar.datasource;
 
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
import javax.sql.DataSource;
import java.io.IOException;
 
/**
 * @author xiawei
 * @date 2020/8/9 15:03
 */
@Configuration
@MapperScan(basePackages = {"cn.com.geostar.dao"},
        sqlSessionFactoryRef = "sqlSessionFactoryBean")
@EnableTransactionManagement
public class MybatisSqlAndTransactionConfig {
 
    private final Logger logger = LoggerFactory.getLogger(MybatisSqlAndTransactionConfig.class);
 
    @Autowired
    private DataSource dataSource;
 
    @Bean(value = "sqlSessionFactoryBean")
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
        if ("null".equals(dataSource.toString())) {
            logger.warn("sqlSessionFactoryBean init failed , because dataSource is null !");
        }
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setVfs(SpringBootVFS.class);
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("/config/mybatis-config.xml"));
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("/config/mapper/*.xml"));
        return sqlSessionFactoryBean;
    }
 
    @Bean(name = "transactionManagerMybatis")
    public PlatformTransactionManager dataSourceTransactionManager() {
        if ("null".equals(dataSource.toString())) {
            logger.warn("dataSourceTransactionManager init failed , because dataSource is null !");
        }
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

 

posted @ 2022-01-02 14:29  童话述说我的结局  阅读(897)  评论(0编辑  收藏  举报