当前位置: 首页 > 知识库问答 >
问题:

数据库连接池耗尽——Java

司马羽
2023-03-14

我在应用程序中使用连接池(snaq.db.ConnectionPool)。连接池的初始化方式如下:

String dburl = propertyUtil.getProperty("dburl");
String dbuserName = propertyUtil.getProperty("dbuserName");
String dbpassword = propertyUtil.getProperty("dbpassword");
String dbclass = propertyUtil.getProperty("dbclass");
String dbpoolName = propertyUtil.getProperty("dbpoolName");
int dbminPool = Integer.parseInt(propertyUtil.getProperty("dbminPool"));
int dbmaxPool = Integer.parseInt(propertyUtil.getProperty("dbmaxPool"));
int dbmaxSize = Integer.parseInt(propertyUtil.getProperty("dbmaxSize"));
long dbidletimeout = Long.parseLong(propertyUtil.getProperty("dbidletimeout"));
Class.forName(dbclass).newInstance();
ConnectionPool moPool = new ConnectionPool(dbpoolName, dbminPool, dbmaxPool, dbmaxSize,
dbidletimeout, dburl, dbuserName, dbpassword);

使用的DB池值是:

dbminPool=5
dbmaxPool=30
dbmaxSize=30
dbclass=org.postgresql.Driver
dbidletimeout=25

我的应用程序在某处泄漏连接(连接未被释放),因此连接池已耗尽。我现在已经修好了密码。

空闲超时后连接不应该关闭吗?如果这不是正确的假设,有没有办法关闭打开的空闲连接(仅通过java代码)?

共有3个答案

何昆
2023-03-14

连接池的全部意义在于让连接池为您处理所有这些事情。

  • 在您的情况下,使用代码关闭java池中打开的空闲连接对您没有帮助
  • 考虑一下连接池维护空闲在用连接的映射
  • IN-USE:如果应用程序正在引用一个连接对象,它将被池放入IN-USE map
  • IDLE:如果一个连接对象没有被应用程序引用/或关闭,它将被池放入IDLE map
  • 您的池已耗尽,因为您没有关闭连接。未关闭连接导致所有空闲连接被放入在用映射
  • 由于idle pool没有任何可用条目,pool被迫创建更多条目
  • 这样,您的所有连接都被标记为正在使用
  • 您的池没有任何打开的空闲连接,您可以通过代码关闭这些连接
  • 即使发生超时,池也无法关闭任何连接,因为没有任何内容处于空闲状态

在修复代码中的连接泄漏时,您已经尽了最大努力。

您可以强制释放池并重新创建一个池。但是你必须小心,因为正在使用的现有连接可能会在执行任务时受到影响。

卫君博
2023-03-14

你没有发布完整的代码,所以我假设你没有关闭连接。仍然需要关闭从池中获取的连接对象,就像不使用池时一样。关闭连接使池可以重新发送给另一个调用方。如果您未能做到这一点,您最终将消耗池中所有可用的连接。池中陈旧的连接清理器不是清理连接的最佳场所。就像你妈妈告诉你的,收拾完东西后把它们收起来。

try {
  conn = moPool.getConnection(timeout);
  if (conn != null)
    // do something
} catch (Exception e) {
  // deal with me
} finally {
  try { 
    conn.close(); 
  } catch (Exception e) { 
    // maybe deal with me
  }
}
  • E
陆洛城
2023-03-14

timeout变量似乎不对应于连接空闲的时间,而是对应于池可以等待多长时间来返回新连接或抛出异常(我看了一下这个源代码,不知道它是否是最新的)。我认为跟踪“空闲”连接会相当困难,因为在这种情况下“空闲”的真正含义是什么?您可能希望获得一个连接以供以后使用。所以我想说连接池知道您完成连接的唯一安全方法是调用关闭()

如果您担心开发团队忘记在他们的代码中调用关闭(),我在下面描述了一种技术,并且我过去曾使用过(在我的案例中,我们希望跟踪未关闭的InputStreams,但概念是相同的)。

免责声明:

  • 我假设这些连接仅在单个请求期间使用,而不在连续请求期间使用。在后一种情况下,您不能使用下面的解决方案

综上所述,其主要思想是:我们有一个中心位置(连接池),从中获取资源(连接),我们希望跟踪这些资源是否由我们的代码释放。我们可以使用一个web过滤器,它使用一个ThreadLocal对象来跟踪请求期间使用的连接。我将这个类命名为TrackingFilter,跟踪资源的对象是Tracker类。

public class TrackingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Tracker.start();
        try {
            chain.doFilter(request, response);
        } finally {
            Tracker.stop();
        }
    }

    ...
}

为了让跟踪器能够跟踪连接,每次使用getConnection()获取连接时,以及每次使用close()调用关闭连接时,都需要通知跟踪器。为了以对其余代码透明的方式实现这一点,我们需要包装ConnectionPool和返回的Connection对象。您的代码应该返回新的TrackingConnectionPool,而不是原始池(我假设访问连接池的方法是在一个地方)。这个新池将依次包装它提供的每个连接,作为可跟踪连接TrackableConnection是一个对象,它知道如何在创建和关闭时通知我们的跟踪器。

当您在请求末尾调用Tracker.stop()时,它将报告尚未调用关闭()的任何连接。由于这是按请求操作,您将仅识别错误操作(即在“创建新产品”功能期间),然后希望您能够跟踪那些留下打开连接并修复它们的查询。

在下面,您可以找到TrackingConnectionPoolTrackableConnectionTracker类的代码和注释。为了简洁起见,省略了委托方法。我希望这能有所帮助。

注意:对于包装器,请使用自动IDE功能(如Eclipse的“生成委托方法”),否则这将是一项耗时且容易出错的任务。

//------------- Pool Creation

ConnectionPool original = new ConnectionPool(String dbpoolName, ...); 
TrackingConnectionPool trackingCP = new TrackingConnectionPool(original);

// ... or without creating the ConnectionPool yourself
TrackingConnectionPool trackingCP = new TrackingConnectionPool(dbpoolName, ...);

// store the reference to the trackingCP instead of the original

//------------- TrackingConnectionPool

public class TrackingConnectionPool extends ConnectionPool {

    private ConnectionPool originalPool;  // reference to the original pool

    // Wrap all available ConnectionPool constructors like this
    public TrackingConnectionPool(String dbpoolName, ...) {
        originalPool = new ConnectionPool(dbpoolName, ...);
    }

    // ... or use this convenient constructor after you create a pool manually
    public TrackingConnectionPool(ConnectionPool pool) {
        this.originalPool = pool; 
    }

    @Override
    public Connection getConnection() throws SQLException {
        Connection con = originalPool.getConnection();
        return new TrackableConnection(con);   // wrap the connections with our own wrapper
    }
    @Override
    public Connection getConnection(long timeout) throws SQLException {
        Connection con = originalPool.getConnection(timeout);
        return new TrackableConnection(con);   // wrap the connections with our own wrapper
    }

    // for all the rest public methods of ConnectionPool and its parent just delegate to the original
    @Override
    public void setCaching(boolean b) {
        originalPool.setCaching(b);
    }
    ...
}

//------------- TrackableConnection

public class TrackableConnection implements Connection, Tracker.Trackable {

    private Connection originalConnection;
    private boolean released = false;

    public TrackableConnection(Connection con) {
        this.originalConnection = con;
        Tracker.resourceAquired(this);  // notify tracker that this resource is aquired
    }

    // Trackable interface

    @Override
    public boolean isReleased() {
        return this.released;
    }

    // Note: this method will be called by Tracker class (if needed). Do not invoke manually
    @Override
    public void release() {
        if (!released) {
            try {
                // attempt to close the connection
                originalConnection.close();
                this.released = true;  
            } catch(SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    // Connection interface

    @Override
    public void close() throws SQLException {   
        originalConnection.close();
        this.released = true;   

        Tracker.resourceReleased(this); // notify tracker that this resource is "released"
    }

    // rest of the methods just delegate to the original connection

    @Override
    public Statement createStatement() throws SQLException {
        return originalConnection.createStatement();
    }
    ....
}

//------------- Tracker

public class Tracker {

    // Create a single object per thread
    private static final ThreadLocal<Tracker> _tracker = new ThreadLocal<Tracker>() {
            @Override 
            protected Tracker initialValue() {
                return new Tracker();
            };
    };

    public interface Trackable {
        boolean isReleased();
        void release();
    }

    // Stores all the resources that are used during the thread.
    // When a resource is used a call should be made to resourceAquired()
    // Similarly when we are done with the resource a call should be made to resourceReleased()
    private Map<Trackable, Trackable> monitoredResources = new HashMap<Trackable, Trackable>();

    // Call this at the start of each thread. It is important to clear the map
    // because you can't know if the server reuses this thread
    public static void start() {
        Tracker monitor = _tracker.get();
        monitor.monitoredResources.clear();
    }

    // Call this at the end of each thread. If all resources have been released
    // the map should be empty. If it isn't then someone, somewhere forgot to release the resource
    // A warning is issued and the resource is released.
    public static void stop() {
        Tracker monitor = _tracker.get();
        if ( !monitor.monitoredResources.isEmpty() ) {
            // there are resources that have not been released. Issue a warning and release each one of them
            for (Iterator<Trackable> it = monitor.monitoredResources.keySet().iterator(); it.hasNext();) {
                Trackable resource = it.next();

                if (!resource.isReleased()) {
                    System.out.println("WARNING: resource " + resource + " has not been released. Releasing it now.");
                    resource.release();
                } else {
                    System.out.println("Trackable " + resource 
                            + " is released but is still under monitoring. Perhaps you forgot to call resourceReleased()?");
                }
            }
            monitor.monitoredResources.clear();
        }
    }

    // Call this when a new resource is acquired i.e. you a get a connection from the pool
    public static void resourceAquired(Trackable resource) {
        Tracker monitor = _tracker.get();
        monitor.monitoredResources.put(resource, resource);
    }

    // Call this when the resource is released
    public static void resourceReleased(Trackable resource) {
        Tracker monitor = _tracker.get();
        monitor.monitoredResources.remove(resource);
    }
}

 类似资料:
  • 我在项目中使用ApacheTomcat JDBC连接池。我很困惑,因为在重负下,我一直看到以下错误: 我的期望是,使用池,新连接的请求将被保留在队列中,直到连接可用。相反,当池达到容量时,请求似乎会被拒绝。这种行为可以改变吗? 谢谢, 达尔 这是我的池配置:

  • 我有使用hikari池创建连接池的Spring启动应用程序。我们正在使用postgres sql用于db。当我以低qps命中系统时,请求需要大约200毫秒来执行。当部署一个pod并且qps为15时,事情保持良好状态。但是一旦我将qps增加到20,请求就开始需要大约10秒来处理,连接池变空(java.sql.SQLTransientConntion异常:菲尼克斯-连接不可用,请求在30183毫秒后超

  • 我有一个Spring启动,Hibernate使用java应用程序。我部署它在一个jetty webserver与多个实例.如果我有太多(大于10)很多实例我得到 许多连接(10x实例)显示为空闲 ps: 实例的Hikari跟踪日志: 设置 没有记录任何有趣的事情。我认为这看起来很有趣-连接不可用 有什么办法可以调试这个吗?我也在java 7上,所以hikari 2.4.7

  • 问题内容: 我在GlassFish上有一个Java-JSF Web应用程序,我想在其中使用连接池。因此,我创建了一个有范围的Bean,可与其他Bean的实例一起使用: 这样,连接池很快就会被填满。在“ db-related”视图中进行几次导航后,应用程序将停止以下操作: RAR5117:无法从连接池[mysql_testPool]获取/创建连接。原因:使用中的连接等于最大池大小和已过期的最大等待时

  • 在为了使用多线程而修改了一个服务方法之后,我发现如果不止一个用户多次尝试请求页面(并调用服务方法),服务器就会抛出“无法连接,池耗尽”异常。让我提供一个我的服务类的例子。 我已经在这个问题上挣扎了一个多星期,我找不到解决方案。我不太明白Grails如何与会话、连接和事务一起工作。我的猜测是跟随。当调用ConvertDocumentToJSON时,它从池中获取连接(4个用户,每个用户25个线程=10

  • null null 额外信息:没有其他异常抛出,我知道。所有其他数据都被正确地检索。派人帮忙。 更新:我做了一些更多的实验:这个应用程序使用了另一个dao,我之前没有提到,因为我忘了。它的工作方式几乎相同,只是连接到一个不同的数据库,所以它有一个单独的配置。它还利用了JdbcNamedTemplate和@Qualifier来选择正确的模板。 现在,我发现,使一个或另一个DAO失效将不再吃连接。所以