【问题标题】:Pool empty. Unable to fetch a connection in 10 seconds池空。无法在 10 秒内获取连接
【发布时间】:2012-12-08 05:12:43
【问题描述】:

运行一段时间后,当我使用至少 20 个浏览器选项卡同时访问 servlet 对我的 servlet 进行压力测试时遇到此错误:

java.sql.SQLException:[tomcat-http--10] 超时:池为空。无法在 10 秒内获取连接,无可用 [size:200;忙:200;空闲:0; lastwait:10000]。

这是用于此的 XML 配置:

<Resource name="jdbc/MyAppHrd"
          auth="Container"
          type="javax.sql.DataSource"
          factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
          testWhileIdle="true"
          testOnBorrow="true"
          testOnReturn="false"
          validationQuery="SELECT 1"
          validationInterval="30000"
          timeBetweenEvictionRunsMillis="30000"
          maxActive="200"
          minIdle="10"
          maxWait="10000"
          initialSize="200"
          removeAbandonedTimeout="120"
          removeAbandoned="true"
          logAbandoned="false"
          minEvictableIdleTimeMillis="30000"
          jmxEnabled="true"
          jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;
            org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
          username="sa"
          password="password"
          driverClassName="net.sourceforge.jtds.jdbc.Driver"
          url="jdbc:jtds:sqlserver://192.168.114.130/MyApp"/>

可能是什么问题?

更新: Java 代码:

public class MyServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private static final Log LOGGER = LogFactory.getLog(MyServlet.class);

   private void doRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        CallableStatement stmt = null;
        ResultSet rs = null;

        Connection conn = null;
        try {

            conn = getConnection();

            stmt = conn.prepareCall("{call sp_SomeSPP(?)}");
            stmt.setLong(1, getId());

            rs = stmt.executeQuery();

            // set mime type
            while (rs.next()) {
                if (rs.getInt(1)==someValue()) {
                    doStuff();
                    break;
                }
            }
            stmt = conn.prepareCall("{call sp_SomeSP(?)}");
            stmt.setLong(1, getId());

            rs = stmt.executeQuery();
            if (rs.next()) {
                // do stuff
            }

            RequestDispatcher rd = getServletContext().getRequestDispatcher("/SomeJSP.jsp");
            rd.forward(request, response);
            return;
        } catch (NamingException e) {
            LOGGER.error("Database connection lookup failed", e);
        } catch (SQLException e) {
            LOGGER.error("Query failed", e);
        } catch (IllegalStateException e) {
            LOGGER.error("View failed", e);
        } finally {
            try {
                if (rs!=null && !rs.isClosed()) {
                    rs.close(); 
                }
            } catch (NullPointerException e) {
                LOGGER.error("Result set closing failed", e);
            } catch (SQLException e) {
                LOGGER.error("Result set closing failed", e);
            }
            try {
                if (stmt!=null) stmt.close();
            } catch (NullPointerException e) {
                LOGGER.error("Statement closing failed", e);
            } catch (SQLException e) {
                LOGGER.error("Statement closing failed", e);
            }
            try {
                if (conn != null){
                    conn.close();
                    conn = null;
                }
            } catch (NullPointerException e) {
                LOGGER.error("Database connection closing failed", e);
            } catch (SQLException e) {
                LOGGER.error("Database connection closing failed", e);
            }
        }

   }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doRequest(request, response);
    }

    protected static Connection getConnection() throws NamingException, SQLException {
        InitialContext cxt = new InitialContext();
        String jndiName = "java:/comp/env/jdbc/MyDBHrd";
        ConnectionPoolDataSource dataSource = (ConnectionPoolDataSource) cxt.lookup(jndiName);
        PooledConnection pooledConnection = dataSource.getPooledConnection();
        Connection conn = pooledConnection.getConnection();
        return conn; // Obtain connection from pool
    }   

【问题讨论】:

  • 你可能有未关闭的连接
  • 我确定我有 try-finally 块,其中 db 的东西在 try 中完成,最后,我调用 conn.close(),我确实设置了一个断点来测试是否每次刷新都会调用 close()
  • 所以池子可能不够大,无法承受您的负载?
  • 您能否提供一些您实际使用连接的代码?您提到的声明只是指出您在池中的最大连接数为 200 个。我希望这足以为 20 个浏览器选项卡提供服务,否则您将不得不担心您的编码风格(这是我怀疑的)。正如 Aviram Segal 所提到的,它很可能是一堆未关闭的连接。或者你那里有长时间运行的进程。
  • @OlafKock 我已经用相关的 java 代码更新了帖子。 doRequest() 非常简单,它从快速返回数据的存储过程中获取数据。这里并没有真正进行任何密集计算。

标签: java tomcat jdbc


【解决方案1】:

检查您的 jdbc 连接在进程完成后是否关闭。可能是连接未关闭造成的。

【讨论】:

    【解决方案2】:

    可能是您保持连接的时间过长。

    确保在开始处理请求时不要打开数据库连接,然后在最终提交响应时释放它。

    典型错误是:

        @Override
        protected void doGet (
                final HttpServletRequest request,
                final HttpServletResponse response
            ) throws
                ServletException,
                IOException
        {
            Connection conn = myGetConnection( );
    
            try
            {
                ...
                // some request handling
    
    
            }
            finally
            {
                conn.close( )
            }
        }
    

    在这段代码中,数据库连接的生命周期完全取决于连接到服务器的客户端。

    更好的模式是

        @Override
        protected void doGet (
                final HttpServletRequest request,
                final HttpServletResponse response
            ) throws
                ServletException,
                IOException
        {
            // some request preprocessing
            MyProcessedRequest parsedInputFromRequest =
                getInputFromRequest( request );
    
            final MyModel model;
            {
               // Model generation
               Connection conn = myGetConnection( );
    
               try
               {
                  model = new MyModel( conn, parsedInputFromRequest );
               }
               finally
               {
                  conn.close( );
               }
            }
    
    
            generateResponse( response, model );         
        }
    

    请注意,如果瓶颈在于模型生成,您仍然会用完连接,但这现在是 DBA 的问题,这与数据库端更好的数据管理/索引有关。

    【讨论】:

      【解决方案3】:

      在执行以下操作之前关闭您的连接。

      RequestDispatcher rd = getServletContext().getRequestDispatcher("/SomeJSP.jsp");
              rd.forward(request, response);
              return;
      

      如果不需要,也删除 return。

      【讨论】:

      • 你的意思是在转发调用之前放置另一组try块来关闭ResultSet、Statement和Connection?还是将转发代码放在 finally 块中?
      • 您首先关闭连接,然后执行请求转发部分。
      • 只在转发前调用 conn.close() ?不关闭 ResultSet 然后 CallableStatement?
      • 对不起,你可以关闭所有使用的资源,conn,rs,cs,stmt
      • 好的,添加了这一行:DbUtils.closeQuietly(conn);在前转呼叫之前。仍然遇到池空问题
      【解决方案4】:

      您当前提供的代码看起来很长/很复杂,但很好。

      不过,我猜您的“doStuff”方法可能会导致更多连接泄漏

      【讨论】:

      • doStuff() 函数实际上只是一行代码的赋值。实际的行只是一个赋值:lastUpdated = rs.getTimestamp("LastUpdated");
      • 所以 - 您显然可以访问那里的数据库...愿意显示代码吗?
      • SQL存储过程是一个T-SQL,带有调用MAX()函数的基本代码
      【解决方案5】:

      我建议您将 getConnection 方法更改为以下内容,您实际上可能会通过直接通过 javax.sql.PooledConnection 接口删除池支持

              InitialContext cxt = new InitialContext();
              String jndiName = "java:/comp/env/jdbc/MyDBHrd";
              DataSource dataSource = (DataSource) cxt.lookup(jndiName);
              return dataSource.getConnection();
      

      还可以使用 DBUtils#closeQuietly 之类的东西来清理您的连接

      更新:您正在从连接中删除池支持。如果您运行以下命令并查看输出,您将看到直接从 DataSource 检索的连接是包装了 PooledConnection 的 ProxyConnection。

      public static void main(String[] args) throws Exception {
      
          Properties properties = new Properties();
          properties.put("username", "sa");
          properties.put("password", "password");
          properties.put("driverClassName", "net.sourceforge.jtds.jdbc.Driver");
          properties.put("url", "jdbc:jtds:sqlserver://192.168.114.130/MyApp");       
      
          DataSourceFactory dsFactory = new DataSourceFactory();      
          DataSource ds = dsFactory.createDataSource(properties);     
          ConnectionPoolDataSource cpds = (ConnectionPoolDataSource) ds;
          PooledConnection pooledConnection = cpds.getPooledConnection();
      
          System.out.println("Pooled Connection - [" + ds.getConnection() + "]"); // Close will return to the Pool
          System.out.println("Internal Connection - [" + pooledConnection.getConnection() + "]"); // Close will just close the connection and not return to pool
      
      }
      

      【讨论】:

      • 你的意思是,不使用: Connection conn = pooledConnection.getConnection(); ?
      • 我没明白你的意思?
      • 如果我使用上面的代码,我会得到这个错误:java.sql.SQLException: Connection has already been closed.
      • 如果您只是将更改后的代码重新部署到正在运行的 tomcat 上,那么您之前可能已经关闭了与旧代码的连接。重启tomcat再试一次,这会从一个新的连接池开始
      • 我已经在本地模拟了一个快速测试,您正在通过 ConnectionPoolDataSource#getPolledConnection#getConnection 从连接中删除池支持。 SQLException 可能是由于您没有使用 Tomcat 正确重新启动池。
      【解决方案6】:

      首先,您没有在方法体中关闭 StatementResultSet 对象。

      当您在Connection 上调用close(根据 JDBC 规范)时,它们应该被清理,但在池设置中,它们实际上可能不会被清理。

      其次,您将解开池连接并返回底层连接,这将破坏一切。

      所以,修改你的代码如下:

       try {
              conn = getConnection();
      
              stmt = conn.prepareCall("{call sp_SomeSPP(?)}");
              stmt.setLong(1, getId());
      
              rs = stmt.executeQuery();
      
              // set mime type
              while (rs.next()) {
                  if (rs.getInt(1)==someValue()) {
                      doStuff();
                      break;
                  }
              }
      
              // ADD THESE LINES
              rs.close(); rs = null;
              stmt.close(); stmt = null;
      
              stmt = conn.prepareCall("{call sp_SomeSP(?)}");
              stmt.setLong(1, getId());
      
              rs = stmt.executeQuery();
              if (rs.next()) {
                  // do stuff
              }
      }
      
      ....
      
      protected static Connection getConnection() throws NamingException, SQLException {
          InitialContext cxt = new InitialContext();
          String jndiName = "java:/comp/env/jdbc/MyDBHrd";
          DataSource dataSource = (DataSource) cxt.lookup(jndiName);
          return dataSource.getPooledConnection();
      }   
      

      而且,正如其他人所说,您肯定希望在进行诸如转发到另一个页面之类的操作之前清理您的资源。否则,您将连接保持得比必要的时间长得多。它是一种关键资源:像对待它一样对待它。

      【讨论】:

      • 我修改了我的代码以适合您的代码,这是我得到的: SQLException: Invalid state,Connection 对象已关闭。从网络浏览器(chrome)同时访问时
      • 您是否获得了包含新异常的堆栈跟踪?如果是这样,请将其发布为对您的问题的编辑。如果你调用close() 两次,你会得到这个错误。
      猜你喜欢
      • 2014-02-19
      • 2017-03-31
      • 1970-01-01
      • 2017-01-13
      • 2011-11-08
      • 2018-03-18
      • 1970-01-01
      • 1970-01-01
      • 2021-11-07
      相关资源
      最近更新 更多