2

我在我的应用程序中使用连接池 (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);

使用的数据库池值是:

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

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

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

4

5 回答 5

5

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

如果您担心开发团队忘记调用close()他们的代码,我在下面描述了一种我过去使用过的技术(在我的例子中,我们想跟踪 unclosed InputStreams,但概念是相同的)。

免责声明:

  • 我假设连接仅在单个请求期间使用,并且在连续请求期间不跨越。在后一种情况下,您不能使用下面的解决方案。
  • 您的连接池实现似乎已经使用了与我在下面描述的技术类似的技术(即它已经包装了连接),所以我不可能知道这是否适用于您的情况。我没有测试过下面的代码,我只是用它来描述这个概念。
  • 请仅在您的开发环境中使用它。在生产中,您应该确信您的代码已经过测试并且行为正确。

如上所述,主要思想是:我们有一个中心位置(连接池),从中我们获取资源(连接),并且我们希望跟踪这些资源是否被我们的代码释放。我们可以使用一个网络,该网络Filter使用一个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();
        }
    }

    ...
}

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

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

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

注意:对于包装器,请使用自动化 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);
    }
}
于 2013-08-15T13:08:47.003 回答
3

您没有发布完整的代码,所以我假设您没有关闭您的连接。如果您不使用池,您仍然需要关闭从池中获得的连接对象。关闭连接使池可以重新发出给另一个调用者。如果您不这样做,您最终将消耗池中的所有可用连接。池的陈旧连接清除程序不是清理连接的最佳位置。就像你妈妈告诉你的那样,当你用完东西后就把它们收起来。

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
  }
}
于 2013-08-06T14:03:32.577 回答
1

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

  • 拥有代码对closing open idle connections of java pool您的情况没有帮助。
  • 考虑为IDLEIN-USE连接维护 MAP 的连接池。
  • IN-USE:如果应用程序正在引用连接对象,则将其放入in-use-map池中。
  • IDLE:如果应用程序/或没有引用连接对象closed,则将其放入idle-map池中。
  • 您的池已用尽,因为您没有关闭连接。不关闭连接会导致所有idle连接都放入in-use-map.
  • 由于idle-pool没有任何可用的条目,池被迫创建更多条目。
  • 这样,您的所有连接都被标记为IN-USE.
  • 您的池没有任何open-idle-connections,您可以通过代码关闭。
  • 即使发生超时,池也无法关闭任何连接,因为没有任何东西是空闲的。

当您修复代码中的连接泄漏时,您已尽力而为。

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

于 2013-08-14T09:20:57.597 回答
0

在大多数连接池中,空闲超时是连接池在连接池中空闲的最长时间(等待被请求),而不是使用多长时间(从连接池中签出)。

一些连接池也有超时设置允许连接使用多长时间(例如 DBCP has removeAbandonedTimeout, c3p0 has unreturnedConnectionTimeout),如果这些被启用并且超时已过期,它们将被用户强制撤销并返回到游泳池还是真的关门了。

于 2013-08-15T13:27:58.243 回答
0

log4jdbc可用于通过jdbc.connection记录器缓解连接泄漏故障排除。

这种技术不需要对代码进行任何修改。

于 2014-08-20T12:49:16.147 回答