背景

在一个DB层本是只有MySQL项目中,后期同时引入了Clickhouse作为日志存储的数据库。在每次项目发布时都会抛出异常unregister mbean error提示注销bean异常,具体报错如下:


javax.management.InstanceNotFoundException: com.alibaba.druid:type=DruidDataSource,id=merchantdb
	at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getMBean(DefaultMBeanServerInterceptor.java:1095)
	at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.exclusiveUnregisterMBean(DefaultMBeanServerInterceptor.java:427)
	at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.unregisterMBean(DefaultMBeanServerInterceptor.java:415)
	at com.sun.jmx.mbeanserver.JmxMBeanServer.unregisterMBean(JmxMBeanServer.java:546)
	at com.alibaba.druid.stat.DruidDataSourceStatManager.removeDataSource(DruidDataSourceStatManager.java:202)
	at com.alibaba.druid.pool.DruidDataSource$2.run(DruidDataSource.java:2103)
	at java.security.AccessController.doPrivileged(Native Method)
	at com.alibaba.druid.pool.DruidDataSource.unregisterMbean(DruidDataSource.java:2099)
	at com.alibaba.druid.pool.DruidDataSource.close(DruidDataSource.java:2056)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:339)
	at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:273)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:579)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:551)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1091)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:512)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1084)
	at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1060)
	at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1029)
	at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:948)

随后紧接着就会出现下面异常

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.DataSourceClosedException: dataSource already closed at Tue Jan 23 14:03:26 CST 2024
### The error may exist in com/xhqb/merchant/common/dal/clickhouse/ProductRequestLogMapper.java (best guess)
### The error may involve com.xhqb.merchant.common.dal.clickhouse.ProductRequestLogMapper.insert
### The error occurred while executing an update
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.DataSourceClosedException: dataSource already closed at Tue Jan 23 14:03:26 CST 2024
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
	at com.sun.proxy.$Proxy153.insert(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:271)
	at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:59)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
	at com.sun.proxy.$Proxy173.insert(Unknown Source)

问题排查过程

首先可以确定,是在引入ClickHouse才引发的问题。同时该服务有两个节点,每次出现问题是在服务关闭的时出现的,同时因为是有两个节点,所以该异常出现了两次,且根据异常出现的时间节点是,先出现注销Bean失败,然后在出现后面的数据连接获取失败的问题。

经过排查代码发先在设置数据源的name时,MySQL的和ClickHouse都设置相同的merchantdb作为名称。

Tips:DataSource#name,配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错。

代码配置如下

MySQL

    @Primary
    @Bean(name = "mysqlDataSource")
    public DataSource mysqlDataSource(MySQLDruidProperties properties) {
        DruidConnectionProperties defaultProperties = DruidConnectionProperties.withDefault(new DefaultConnectionProperties());
        BeanUtils.copyProperties(properties, defaultProperties, CommonUtils.getNullPropertyNames(properties));
        DruidDataSource dataSource = DatasourceConfigUtils.createDataSource(defaultProperties);
        dataSource.setName("merchantdb");
        return dataSource;
    }

ClickHouse配置如下

    @Bean(name = "clickHouseDataSource")
    public DataSource clickHouseDataSource(ClickHouseDruidProperties clickHouseDruidProperties) {
        DruidConnectionProperties defaultProperties = DruidConnectionProperties.withDefault(new DefaultConnectionProperties());
        BeanUtils.copyProperties(clickHouseDruidProperties, defaultProperties, CommonUtils
                .getNullPropertyNames(clickHouseDruidProperties));
        DruidDataSource dataSource = DatasourceConfigUtils.createDataSource(defaultProperties);
        dataSource.setName("merchantdb");
        return dataSource;
    }

由于数据源设置了相同的name,导致在数据源销毁的时候,将该name的连接池销毁了。而另外一个一个数据源可能还有进行在执行,导致在获取连接的时候连接池已关闭。在删除该name的手动赋值后,让其自定义设置name,问题得解。

总结

在配置多数据源时尤其要小心连接池的配置,对于name属性如果没有硬性要求多个数据源放入同一个连接池的话可以优先考虑让其自动生成的方式,避免连接池的重名


标题:记一次Druid连接池关闭异常问题
作者:JonLv
地址:http://39.108.183.139:8080/articles/2024/01/23/1706008533843.html