【问题标题】:Java: Concurency with HikariDataSource ObjectJava:与 HikariDataSource 对象的并发性
【发布时间】:2016-01-26 00:38:50
【问题描述】:

我有一个看起来像这样的课程:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

public class ConnectionPool {
    private HikariDataSource hds;
    private final String propertyFileName;

    public ConnectionPool(String propertyFileName) {
        if (propertyFileName == null) {
            throw new IllegalArgumentException("propertyFileName can't be null");
        }

        this.propertyFileName = propertyFileName;
        reloadFile();
    }

    public void reloadFile() {
        if (hds != null) {
            hds.close();
        }

        hds = new HikariDataSource(new HikariConfig(propertyFileName));
    }

    public HikariDataSource getHikariDataSource() {
        return hds;
    }

    public String getPropertyFileName() {
        return propertyFileName;
    }

    public void executeQuery(final String sql, final CallBack<ResultSet, SQLException> callBack) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Connection connection = null;
                PreparedStatement preparedStatement = null;
                ResultSet resultSet = null;

                try {
                    connection = hds.getConnection();
                    preparedStatement = connection.prepareStatement(sql);
                    resultSet = preparedStatement.executeQuery();
                    callBack.call(resultSet, null);
                } catch (SQLException e) {
                    callBack.call(null, e);
                } finally {
                    if (resultSet != null) {
                        try {
                            resultSet.close();
                        } catch (SQLException ignored) {}
                    }

                    if (preparedStatement != null) {
                        try {
                            preparedStatement.close();
                        } catch (SQLException ignored) {}
                    }

                    if (connection != null) {
                        try {
                            connection.close();
                        } catch (SQLException ignored) {}
                    }
                }
            }
        }).start();
    }

    public void executeUpdate(final String sql, final CallBack<Integer, SQLException> callBack) {
        //TODO
    }

    public void execute(final String sql, final CallBack<Boolean, SQLException> callBack) {
        //TODO
    }

    public void connection(final String sql, final CallBack<Connection, SQLException> callBack) {
        //TODO
    }
}

问题是reloadFile() 方法可以在使用hds 时从不同的线程调用。因此,当我在另一个线程中使用它的连接对象时,hds 可能已关闭。解决这个问题的最佳方法是什么?我应该在创建新的HikariDataSource 对象后等待几秒钟,然后再关闭旧对象(直到查询完成)?

编辑:另一个问题:hds 是否应该是volatile,这样hds 的更改对所有线程都是可见的?

【问题讨论】:

  • 对于“易失性”:是的,您应该这样做。但是,将其设置为 volatile 并不能解决所有问题:原始 ds 仍然可能已关闭,但您尚未将其分配给新数据源。在那一刻,正在获得连接的线程将从封闭的数据源中获取,您的代码应该能够应对这种情况。而且,如果您的代码能够应对这种情况,那么不将其更改为 volatile 并不是什么大问题

标签: java concurrency thread-safety connection-pooling hikaricp


【解决方案1】:

HikariDataSource 的源代码中进行了非常快速和简短的查看。在其close() 中,它正在调用其内部HikariPoolshutdown() 方法,为此它将尝试正确关闭池连接。

如果您甚至想避免任何进行中的连接强制关闭的机会,一种方法是使用ReadWriteLock

public class ConnectionPool {
    private HikariDataSource hds;
    private ReentrantReadWriteLock dsLock = ....;
    //....

    public void reloadFile() {
        dsLock.writeLock().lock();
        try {
            if (hds != null) {
                hds.close();
            }

            hds = new HikariDataSource(new HikariConfig(propertyFileName));
        } finally {
            dsLock.writeLock().unlock();
        }
    }

    public void executeQuery(final String sql, final CallBack<ResultSet, SQLException> callBack) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Connection connection = null;
                PreparedStatement preparedStatement = null;
                ResultSet resultSet = null;

                dsLock.readLock().lock();
                try {
                    connection = hds.getConnection();
                    // ....
                } catch (SQLException e) {
                    callBack.call(null, e);
                } finally {
                    // your other cleanups
                    dsLock.readLock().unlock();
                }
            }
        }).start();
    }
    //....
}

这将确保

  1. 多线程可以访问您的数据源(获取连接等)
  2. 数据源的重新加载需要等到使用数据源的线程完成
  3. 重新加载时,没有线程能够使用数据源获取连接。

【讨论】:

  • 这就是我正在寻找的,但如果可能的话,我更喜欢一个不会​​改变我的executeQuery() 代码的解决方案。有没有办法检查池中是否有(打开的)连接(等待关闭数据源,直到所有连接都“回到池中”)?我是否必须使 hds 不稳定,以便 reloadFile() 之后的更改对所有线程都可见?
  • 一种方法:在 HikariDataSource 上进行装饰(将其视为 DataSource 而不是在此处引用具体类会更好),为此将执行readLock().lock() 然后返回一个装饰的Connection 对象.装饰连接将在其close() 中调用readLock().unlock()
  • 试图深入研究HikariDataSource 代码,但尚未找到控制其关闭行为的方法。您可以查看HikraiPool#shutdown() 以了解它是如何执行的,看看是否有机会找到一些属性来控制其行为。但是,您真的尝试过默认行为吗?它真的给你带来任何问题吗?因为从代码来看,在数据源关闭时,您的另一个线程似乎不会使用连接
  • 当然有,当我调用executeQuery(),然后reloadFile()executeQuery() 还没有完成,但已经有来自“旧”数据源的连接对象。我在关闭池后尝试使用池中的连接,结果是SQLException
【解决方案2】:

你究竟为什么要让 HikariCP 重新加载?许多重要的池参数(minimumIdlemaximumPoolSizeconnectionTimeout 等)在运行时可以通过 JMX bean 进行控制,而无需重新启动池。

重新启动池是在关闭和重建连接时“挂起”应用程序几秒钟的好方法。如果你不能通过 JMX 接口做你需要的事情,Adrian 的建议似乎是一个相当合理的解决方案。

其他解决方案是可能的,但更复杂。

编辑:只是为了我自己的娱乐,这里是更复杂的解决方案......

public class ConnectionPool {
   private AtomicReference<HikariDataSource> hds;

   public ConnectionPool(String propertyFileName) {
      hds = new AtomicReference<>();
      ...
   }

   public void reloadFile() {
      final HikariDataSource ds = hds.getAndSet(new HikariDataSource(new HikariConfig(propertyFileName)));
      if (ds != null) {
         new Thread(new Runnable() {
           public void run() {
              ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + ds.getPoolName() + ")");
              MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
              HikariPoolMXBean poolProxy = JMX.newMXBeanProxy(mBeanServer, poolName, HikariPoolMXBean.class);

              poolProxy.softEvictConnections();
              do {
                 Thread.sleep(500);
              } while (poolProxy.getActiveConnections() > 0);
              ds.close();
           }
         }).start();
       }
   }

   public HikariDataSource getHikariDataSource() {
      return hds.get();
   }

   public void executeQuery(final String sql, final CallBack<ResultSet, SQLException> callBack) {
      new Thread(new Runnable() {
         @Override
         public void run() {
            ...
            try {
               connection = getHikariDataSource().getConnection();
               ...
            }
         }
      }).start();
   }
}

这将换出池(以原子方式)并启动一个线程,等待所有活动连接都返回,然后关闭孤立池实例。

这假设您让 HikariCP 生成唯一的池名称,即不在您的属性中设置 poolName,而 registerMbeans=true

【讨论】:

  • 谢谢!回答您的问题:我为 spigot 服务器创建了一个插件,它处理其他插件(在同一台服务器上运行)可以使用的所有数据库连接池。用户可以在属性文件中指定连接池(ip、端口、数据库、用户……)。所有池都通过其String name(键)存储在Map 中。应该有一个选项可以随时重新加载属性文件,例如,如果您添加或更改文件。但正在进行的连接此时不应失败。所以每次关闭池时我都必须检查是否没有这样的连接。这是一种 API。
【解决方案3】:

几个选项:

同步所有对数据源的访问,以便只有一个线程可以处理它。不可扩展,但可行。

滚动您自己的连接池,例如 Apache Commons Pooling,以便每次访问(无论线程如何)都请求数据源,并且池会根据需要创建一个数据源。可以乱数据ACID,只看是否需要脏数据,何时刷新数据,事务性等。

每个线程也可以使用 ThreadLocal 拥有自己的数据源,以便每个线程完全相互独立。同样,数据质量可能是一个问题,如果您有“大量”线程(取决于您的定义)并且打开的连接过多会导致客户端或服务器上的资源问题,资源可能是一个问题。

【讨论】:

    猜你喜欢
    • 2020-11-25
    • 2013-02-10
    • 1970-01-01
    • 1970-01-01
    • 2010-12-26
    • 1970-01-01
    • 2020-09-09
    • 1970-01-01
    • 2020-08-30
    相关资源
    最近更新 更多