普罗米修斯监测nacos导致微服务启动异常

请问大家是否遇到过在生产环境中,nacos监测通过正常url是可以正常访问数据的

img

但是将监测信息配置到普罗米修斯的配置文件中,就会导致,向这个正在被监测中nacos注册的服务,都会启动失败,无法正常启动成功是什么原因。

【相关推荐】



  • 这篇博客: 如何使用Nacos实现数据库连接的自动切换?中的 数据库宕机后,如何更新nacos配置实现数据库连接的自动切换? 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:

    基于mybatis plus,一主一从的配置来实现数据源的配置的自动切换。mybatis plus的主库master,从库slave_1的配置如下:

    spring.datasource.dynamic.primary=master
    spring.datasource.dynamic.datasource.master.username=root
    spring.datasource.dynamic.datasource.master.password=123456
    spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.dynamic.datasource.master.url=jdbc:mysql://127.0.0.1:3306/smcx_farm_products_mall?autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    
    spring.datasource.dynamic.datasource.slave_1.username=root
    spring.datasource.dynamic.datasource.slave_1.password=root
    spring.datasource.dynamic.datasource.slave_1.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.dynamic.datasource.slave_1.url=jdbc:mysql://127.0.0.1:3306/smcx_farm_products_mall?autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai

    分析mybatis plus源码知道,mybatis plus定义DynamicRoutingDataSource实现数据源的动态切换,该类继承AbstractRoutingDataSource实现的,在获取数据库链接Connection时,根据配置判断从主从数据源获取连接,AbstractRoutingDataSource部分源码如下:

    public abstract class AbstractRoutingDataSource extends AbstractDataSource {
    
      /**
       * 子类实现决定最终数据源
       *
       * @return 数据源
       */
      protected abstract DataSource determineDataSource();
    
      @Override
      public Connection getConnection() throws SQLException {
    
        return determineDataSource().getConnection();
      }
    
      @Override
      public Connection getConnection(String username, String password) throws SQLException {
        return determineDataSource().getConnection(username, password);
      }
    }

    分析mybatis plus配置类DynamicDataSourceAutoConfiguration,可以知道,只需要实现可以监听nacos参数配置,可以动态切换数据源配置的DataSource即可,DynamicDataSourceAutoConfiguration的部分源码如下:

    @Configuration
    @EnableConfigurationProperties(DynamicDataSourceProperties.class)
    @AutoConfigureBefore(DataSourceAutoConfiguration.class)
    @Import(DruidDynamicDataSourceConfiguration.class)
    @ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
    public class DynamicDataSourceAutoConfiguration {
    
        
     /**
       * 数据源配置
       *
       */
      @Autowired
      private DynamicDataSourceProperties properties;
    
    
      @Bean
      @ConditionalOnMissingBean
      public DynamicDataSourceProvider dynamicDataSourceProvider() {
        return new YmlDynamicDataSourceProvider(properties);
      }
    
    
    /**
       * 在spring中注入动态数据源,注入自定义的DataSource即可扩展
       *
       */
      @Bean
      @ConditionalOnMissingBean
      public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(properties.getPrimary());
        dataSource.setStrategy(properties.getStrategy());
        dataSource.setProvider(dynamicDataSourceProvider);
        dataSource.setP6spy(properties.getP6spy());
        dataSource.setStrict(properties.getStrict());
        return dataSource;
      }
     }

    定义DynamicRoutingAndSwitchingDataSource,实现数据源动态路由,数据库配置自动切换。采用装饰模式扩展mybatis plus的DynamicRoutingDataSource的功能,实现EnvironmentChangeEvent事件的监听器ApplicationListener,在nacos配置修改的时候,监听配置的修改。Nacos配置在触发事件时,只会传输更新值的key/value值,所以,需要根据更新属性的key值,判断是否修改的数据库配置。其源码如下:

    @Slf4j
    public class DynamicRoutingAndSwitchingDataSource extends AbstractRoutingDataSource implements ApplicationListener<EnvironmentChangeEvent>, EnvironmentAware,
            InitializingBean, DisposableBean {
    
        /**
         * mybatis plus 动态数据源Properties
         */
        private DynamicDataSourceProperties dynamicDataSourceProperties;
        /**
         * 多数据源加载接口
         */
        private YmlDynamicDataSourceProvider dynamicDataSourceProvider;
        /**
         * 动态数据源Properties 属性key集合
         */
        private Set<String> propertyKeys;
        /**
         * mybatis plus动态路由数据源,使用装饰模式修饰
         */
        private DynamicRoutingDataSource dynamicRoutingDataSource;
        /**
         * spring Environment 配置对象
         */
        private Environment environment;
    
        public DynamicRoutingAndSwitchingDataSource(DynamicDataSourceProperties properties, DynamicDataSourceProvider provider) {
    
            this.dynamicDataSourceProperties = properties;
            this.dynamicDataSourceProvider = (YmlDynamicDataSourceProvider) provider;
            this.dynamicRoutingDataSource = this.buildDynamicRoutingDataSource(properties, provider);
            this.propertyKeys = Sets.newHashSet(
                    ConfigProperty.MASTER_USERNAME_KEY, ConfigProperty.MASTER_PASSWORD_KEY,
                    ConfigProperty.MASTER_DRIVER_CLASS_NAME_KEY, ConfigProperty.MASTER_RL_KEY,
                    ConfigProperty.SLAVE_1_USERNAME_KEY, ConfigProperty.SLAVE_1_PASSWORD_KEY,
                    ConfigProperty.SLAVE_1_DRIVER_CLASS_NAME_KEY, ConfigProperty.SLAVE_1_RL_KEY);
    
        }
    
        private DynamicRoutingDataSource buildDynamicRoutingDataSource(DynamicDataSourceProperties dynamicDataSourceProperties, DynamicDataSourceProvider provider) {
    
            DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
            dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
            dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
            dataSource.setProvider(provider);
            dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
            dataSource.setStrict(dynamicDataSourceProperties.getStrict());
            return dataSource;
        }
       /**
         * 监听配置修改的EnvironmentChangeEvent事件
         */
        @Override
        public void onApplicationEvent(EnvironmentChangeEvent event) {
    
            Set<String> keys;
            if (isNotDatasourceConfigChanged(keys = event.getKeys())) {
    
                log.info("keys {} are not database config!", JSON.toJSONString(keys));
                return;
            }
            DynamicDataSourceProperties refreshProperties = this.dynamicDataSourceProperties;
            Map<String, DataSourceProperty> datasource = refreshProperties.getDatasource();
            Map<String, Properties> changedPropertiesMap = this.getChangedProperties();
    
            if (log.isDebugEnabled()) {
                log.debug("changed properties {}", JSON.toJSONString(changedPropertiesMap));
            }
            for (Map.Entry<String, DataSourceProperty> entry : datasource.entrySet()) {
    
                String nodeName = entry.getKey();
                Properties properties;
                if (Objects.isNull(properties = changedPropertiesMap.get(nodeName))) {
    
                    log.info("node {} has no properties!changedPropertiesMap {}", nodeName, JSON.toJSONString(changedPropertiesMap));
                    continue;
                }
                DataSourceProperty dataSourceProperty = entry.getValue();
                String userName;
                if (StringUtils.isNotBlank(userName = properties.getProperty(ConfigProperty.USERNAME))) {
                    dataSourceProperty.setUsername(userName);
                }
                String password;
                if (StringUtils.isNotBlank(password = properties.getProperty(ConfigProperty.PASSWORD))) {
                    dataSourceProperty.setPassword(password);
                }
                String driverClassName;
                if (StringUtils.isNotBlank(driverClassName = properties.getProperty(ConfigProperty.DRIVER_CLASS_NAME))) {
                    dataSourceProperty.setDriverClassName(driverClassName);
                }
                String url;
                if (StringUtils.isNotBlank(url = properties.getProperty(ConfigProperty.URL))) {
                    dataSourceProperty.setUrl(url);
                }
            }
    
            try {
    
                // 使用反射设置properties,避免初始化后,spring自动注入的DynamicDataSourceCreator无法获取
                // @Autowired
                // private DynamicDataSourceCreator dynamicDataSourceCreator;
                Reflections.setFieldValue(this.dynamicDataSourceProvider, "properties", refreshProperties);
                this.dynamicRoutingDataSource = this.buildDynamicRoutingDataSource(refreshProperties, this.dynamicDataSourceProvider);
                // 重置数据源
                this.dynamicRoutingDataSource.afterPropertiesSet();
            } catch (Exception e) {
    
                throw new RuntimeException("refresh dynamic routing switch datasource error!", e);
            }
        }
    
        private boolean isNotDatasourceConfigChanged(Set<String> keys) {
    
            for (String key : keys) {
                if (propertyKeys.contains(key)) {
                    return false;
                }
            }
            return true;
        }
    
        private boolean isMasterKey(String key) {
            return key.contains(ConfigProperty.MASTER);
        }
    
        private boolean isSlave1Key(String key) {
            return key.contains(ConfigProperty.SLAVE_1);
        }
    
        private boolean isUsernameKey(String key) {
            return key.contains(ConfigProperty.USERNAME);
        }
    
        private boolean isPasswordKey(String key) {
            return key.contains(ConfigProperty.PASSWORD);
        }
    
        private boolean isDriverClassNameKey(String key) {
            return key.contains(ConfigProperty.DRIVER_CLASS_NAME);
        }
    
        private boolean isUrlKey(String key) {
            return key.contains(ConfigProperty.URL);
        }
    
        private Map<String, Properties> getChangedProperties() {
    
            Map<String, Properties> propertiesMap = ImmutableMap.of(ConfigProperty.MASTER, new Properties(), ConfigProperty.SLAVE_1, new Properties());
            for (String key : this.propertyKeys) {
    
                String value;
                if (StringUtils.isBlank(value = this.environment.getProperty(key))) {
                    continue;
                }
                String propertyKey = this.convert2propertyKey(key);
                if (this.isMasterKey(key)) {
                    propertiesMap.get(ConfigProperty.MASTER).setProperty(propertyKey, value);
                } else if (isSlave1Key(key)) {
                    propertiesMap.get(ConfigProperty.SLAVE_1).setProperty(propertyKey, value);
                }
            }
            return propertiesMap;
        }
    
        private String convert2propertyKey(String key) {
    
            String propertyKey = key;
            if (this.isUsernameKey(key)) {
                propertyKey = ConfigProperty.USERNAME;
            } else if (this.isPasswordKey(key)) {
                propertyKey = ConfigProperty.PASSWORD;
            } else if (this.isDriverClassNameKey(key)) {
                propertyKey = ConfigProperty.DRIVER_CLASS_NAME;
            } else if (this.isUrlKey(key)) {
                propertyKey = ConfigProperty.URL;
            }
            return propertyKey;
        }
    
       /**
         * 设置Environment 属性
         */
        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }
    
        /**
         * 调用被装饰的dynamicRoutingDataSource的方法,获取指定的数据源
         */
        @Override
        protected DataSource determineDataSource() {
            return this.dynamicRoutingDataSource.determineDataSource();
        }
    
       /**
         * 调用被装饰的dynamicRoutingDataSource的方法,初始化数据源
         */
        @Override
        public void afterPropertiesSet() throws Exception {
            this.dynamicRoutingDataSource.afterPropertiesSet();
        }
    
        @Override
        public void destroy() throws Exception {
            this.dynamicRoutingDataSource.destroy();
        }
    }
    
    public interface ConfigProperty {
    
        //////////// 数据源属性配置 ////////////
        String MASTER = "master";
        String SLAVE_1 = "slave_1";
        String USERNAME = "username";
        String PASSWORD = "password";
        String DRIVER_CLASS_NAME = "driver-class-name";
        String URL = "url";
    
        //////////// 数据源属性key配置 ////////////
        String MASTER_USERNAME_KEY = "spring.datasource.dynamic.datasource.master." + USERNAME;
        String MASTER_PASSWORD_KEY = "spring.datasource.dynamic.datasource.master." + PASSWORD;
        String MASTER_DRIVER_CLASS_NAME_KEY = "spring.datasource.dynamic.datasource.master." + DRIVER_CLASS_NAME;
        String MASTER_RL_KEY = "spring.datasource.dynamic.datasource.master." + URL;
        String SLAVE_1_USERNAME_KEY = "spring.datasource.dynamic.datasource.slave_1." + USERNAME;
        String SLAVE_1_PASSWORD_KEY = "spring.datasource.dynamic.datasource.slave_1." + PASSWORD;
        String SLAVE_1_DRIVER_CLASS_NAME_KEY = "spring.datasource.dynamic.datasource.slave_1." + DRIVER_CLASS_NAME;
        String SLAVE_1_RL_KEY = "spring.datasource.dynamic.datasource.slave_1." + URL;
    }

    自定义DynamicDataSourceAutoConfiguration,在spring中注入定义的DynamicRoutingAndSwitchingDataSource,其源码如下:

    @Configuration
    @EnableConfigurationProperties(DynamicDataSourceProperties.class)
    @AutoConfigureBefore(DataSourceAutoConfiguration.class)
    @Import(DruidDynamicDataSourceConfiguration.class)
    @ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
    public class DynamicDataSourceAutoConfiguration {
    
        @Autowired
        private DynamicDataSourceProperties properties;
    
        @Bean
        public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
    
            DynamicRoutingAndSwitchingDataSource dataSource = new DynamicRoutingAndSwitchingDataSource(properties, dynamicDataSourceProvider);
            return dataSource;
        }
    }

    结果测试,在nacos服务器修改从数据库的配置,如图:

    代码调试时,后端数据库配置生效信息,如图:


如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^