nacos更新时报错异常排查

1 篇文章 0 订阅
1 篇文章 0 订阅

事情是这样的,近期公司业务原因,要在一个项目中加入多数据源配置,加就加吧,也不麻烦,可是加完多数据源之后业务没出问题,却发现刷新nacos时,原本可以正常refresh值的,现在出现异常报错,只能通过重启保证重新加载更新过的nacos的值了。那怎么办,自己作的死要自己解决。

  1. 异常报错
  2. 数据源DataSource配置
  3. 问题分析
  4. 问题解决

如果没耐性的,可以直接拉到底部看问题解决吧。


异常报错
2021-04-13 17:02:52.962 [tid:  ][skId:  ] [com.alibaba.nacos.client.Worker.longPolling.fixed-ip_10348-ip_10348-ip_10348] ERROR c.a.n.c.config.impl.CacheData:220 - [fixed-ip_10348-ip_10348-ip_10348] [notify-error] dataId=arthur-payment-test.yml, group=DEFAULT_GROUP, md5=01bf9cdbf9d77212704149639b020844, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1@1ac9633c tx={}
org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.datasource' to javax.sql.DataSource
        at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:242)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:218)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:202)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:185)
        at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:78)
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:101)
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:89)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:414)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1763)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:405)
        at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:102)
        at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:83)
        at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.onApplicationEvent(ConfigurationPropertiesRebinder.java:128)
        at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.onApplicationEvent(ConfigurationPropertiesRebinder.java:50)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:402)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:359)
        at org.springframework.cloud.context.refresh.ContextRefresher.refreshEnvironment(ContextRefresher.java:96)
        at org.springframework.cloud.context.refresh.ContextRefresher.refresh(ContextRefresher.java:85)
        at org.springframework.cloud.endpoint.event.RefreshEventListener.handle(RefreshEventListener.java:72)
        at org.springframework.cloud.endpoint.event.RefreshEventListener.onApplicationEvent(RefreshEventListener.java:61)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:402)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:359)
        at com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1.innerReceive(NacosContextRefresher.java:133)
        at com.alibaba.nacos.api.config.listener.AbstractSharedListener.receiveConfigInfo(AbstractSharedListener.java:38)
        at com.alibaba.nacos.client.config.impl.CacheData$1.run(CacheData.java:203)
        at com.alibaba.nacos.client.config.impl.CacheData.safeNotifyListener(CacheData.java:233)
        at com.alibaba.nacos.client.config.impl.CacheData.checkListenerMd5(CacheData.java:174)
        at com.alibaba.nacos.client.config.impl.ClientWorker$LongPollingRunnable.run(ClientWorker.java:552)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: Unable to set value for property driver-class-name
        at org.springframework.boot.context.properties.bind.JavaBeanBinder$BeanProperty.setValue(JavaBeanBinder.java:339)
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:86)
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70)
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54)
        at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:329)
        at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
        at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359)
        at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
        at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
        at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
        at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:330)
        at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429)
        at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:415)
        at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:372)
        at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:328)
        at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:269)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214)
        ... 39 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
        at sun.reflect.GeneratedMethodAccessor650.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.context.properties.bind.JavaBeanBinder$BeanProperty.setValue(JavaBeanBinder.java:336)
        ... 59 common frames omitted
Caused by: java.lang.IllegalStateException: The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.
        at com.zaxxer.hikari.HikariConfig.checkIfSealed(HikariConfig.java:1014)
        at com.zaxxer.hikari.HikariConfig.setDriverClassName(HikariConfig.java:472)
        ... 63 common frames omitted
数据源DataSource配置
@Configuration
@MapperScan(value = DBConfiguration.PRIMARY_MAPPER_BASE_PACKAGE, sqlSessionTemplateRef = "sessionTemplate")
public class DBConfiguration {
    public static final String PRIMARY_MAPPER_BASE_PACKAGE = "cn.arthur.payment.biz.dal.mapper";

    public static final String TRANSACTION_MANAGER = "transactionManager";

    @Bean(name="dataSource")
    @ConfigurationProperties(prefix="spring.datasource")
    public DataSource dataSource() {
        return new HikariDataSource();
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath:mapper/*.xml"));
        return sqlSessionFactory.getObject();
    }

    @Bean("sessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(
            @Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(TRANSACTION_MANAGER)
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean("transactionTemplate")
    public TransactionTemplate transactionTemplate(@Qualifier("transactionManager") PlatformTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }
}
问题分析

报错日志可太长了,我们看看最底部的报错日志在说什么.

Caused by: java.lang.IllegalStateException: Unable to set value for property driver-class-name
省略一部分代码
Caused by: java.lang.IllegalStateException: The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.
        at com.zaxxer.hikari.HikariConfig.checkIfSealed(HikariConfig.java:1014)
        at com.zaxxer.hikari.HikariConfig.setDriverClassName(HikariConfig.java:472)
        ... 63 common frames omitted   

翻译翻译就是这个连接池的配置,一旦被创建过了就不能被修改了,导致在设置driver-class-name属性的时候,报错了。那到底是什么情况,我们跟进源码看看。
首先看HikariConfig#checkIfSealed 和 HikariConfig#setDriverClassName方法

  private void checkIfSealed()
  {
     if (sealed) throw new IllegalStateException("The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.");
  }
  
  public void setDriverClassName(String driverClassName)
  {
     checkIfSealed();
     省略一部分代码
  }

那么我们可以知道,其实报错就是因为,sealed这个属性为true,导致了报错,接下去该找找,sealed这个属性什么时候会被更改

代码一:设置为false, 这个方法后续会讲到,注意下
public void copyStateTo(HikariConfig other)
   {
      for (Field field : HikariConfig.class.getDeclaredFields()) {
         if (!Modifier.isFinal(field.getModifiers())) {
            field.setAccessible(true);
            try {
               field.set(other, field.get(this));
            }
            catch (Exception e) {
               throw new RuntimeException("Failed to copy HikariConfig state: " + e.getMessage(), e);
            }
         }
      }

     other.sealed = false;
   }
 代码二:设置为true
 void seal()
   {
     this.sealed = true;
   }

在HikariConfig类中,sealed属性在三个地方被使用,两处是赋值,一处是调用判断。当调用了HikariConfig#seal方法时,会导致后续 setDriverClassName 这个方法会报错。那么我们看看到底哪些地方会调用HikariConfig#seal方法。
还是查看源码

代码一: HikariDataSource的无参初始化
public HikariDataSource()
   {
      super();
      fastPathPool = null;
   }
代码二: HikariDataSource的有参初始化
public HikariDataSource(HikariConfig configuration)
   {
      configuration.validate();
      configuration.copyStateTo(this);

      LOGGER.info("{} - Starting...", configuration.getPoolName());
      pool = fastPathPool = new HikariPool(this);
      LOGGER.info("{} - Start completed.", configuration.getPoolName());

      this.seal();
   }
代码三:创建连接池
   @Override
   public Connection getConnection() throws SQLException
   {
      省略一部分代码
      if (result == null) {
         synchronized (this) {
            result = pool;
            if (result == null) {
               validate();
               LOGGER.info("{} - Starting...", getPoolName());
               try {
                  pool = result = new HikariPool(this);
                  this.seal();
               }
               catch (PoolInitializationException pie) {
                  if (pie.getCause() instanceof SQLException) {
                     throw (SQLException) pie.getCause();
                  }
                  else {
                     throw pie;
                  }
               }
               LOGGER.info("{} - Start completed.", getPoolName());
            }
         }
      }

      return result.getConnection();
   }

我们可以看到,HikariConfig的子类HikariDataSource,在初始化和创建连接池时,都会调用HikariConfig#seal方法,HikariDataSource的初始化可以选择无参初始化方式,但是创建连接池是不可避免的,这问题看起来似乎无解,但未接入多数据源时,spring如何解决这个问题,之前为何不会报错。
仔细看代码,HikariDataSource类中调用seal方法,都是this.seal,而非HikariConfig的实例调用seal方法,换成人话说,就是这里只会影响HikariDataSource类实例的sealed属性啊。我们只需要创建一个HikariConfig的实例,由spring注入,使用HikariDataSource的有参构造方法,那么该HikariConfig 实例的sealed一直是没被设置过的默认初始值false,那么HikariConfig#setDriverClassName方法调用就不会出错,问题不就解决了。
初始化时HikariConfig类实例内部的属性值,为什么会影响到HikariDataSource类实例的属性值呢。我们可以看下HikariDataSource有参初始化时,调用的configuration.copyStateTo(this);这行代码,这个其实调用的是父类HikariConfig的方法,具体的代码上面介绍HikariConfig的时候也摘录了。其实是将HikariConfig实例内的所有field的值,都赋值到HikariDataSource实例上。


代码的介绍到此也结束了,总结下怎么解决nacos更新报错的问题吧。

报错原因如下
  1. 我使用了HikariDataSource的无参构造函数,创建了HikariDataSource的实例bean
  2. 随着程序的运行,创建并获取连接池后,HikariDataSourced实例的sealed属性被设置为true
  3. 更改配置文件后,HikariConfig#setDriverClassName方法被调用,此时其实是HikariDataSourced实例作为调用方,由于步骤2的存在,会使得调用方法报错,nacos更新值报错
解决方法:
  1. 创建HikariConfig实例,交由spring管理,再使用HikariDataSourced有参构造方法,创建HikariDataSourced的实例
  2. 初始化完毕后HikariDataSourced的sealed属性已被赋值成true,意味着他不允许再调用setDriverClassName方法。而此时的HikariConfig实例的sealed属性值仍为初始值false,意味着配置文件更改后,仍能通过nacos影响HikariConfig实例的值的更改
  3. 随着配置文件更改,HikariConfig实例的值被更改后,由于HikariDataSourced实例已存在,不会再次进行初始化,所以HikariDataSourced实例的属性值不会有影响,但是也不会报错了。
问题解决

改变DBConfiguration 配置方式即可,更新后的代码如下

@Configuration
@MapperScan(value = DBConfiguration.PRIMARY_MAPPER_BASE_PACKAGE, sqlSessionTemplateRef = "sessionTemplate")
public class DBConfiguration {
    public static final String PRIMARY_MAPPER_BASE_PACKAGE = "cn.arthur.payment.biz.dal.mapper";

    public static final String TRANSACTION_MANAGER = "transactionManager";

    @Primary
    @Bean(name = "dataSourceConfig")
    @ConfigurationProperties(prefix = "spring.datasource")
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Primary
    @Bean(name = "dataSource")
    public DataSource dataSource(HikariConfig hikariConfig) {
        return new HikariDataSource(hikariConfig);
    }

    @Primary
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath:mapper/*.xml"));
        return sqlSessionFactory.getObject();
    }

    @Primary
    @Bean("sessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(
            @Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Primary
    @Bean(TRANSACTION_MANAGER)
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Primary
    @Bean("transactionTemplate")
    public TransactionTemplate transactionTemplate(
            @Qualifier("transactionManager") PlatformTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值