如何调试/记录Tomcat JDBC连接池的连接?

时间:2016-04-13 01:36:47

标签: debugging tomcat logging log4j connection-pooling

我正在使用Tomcat JDBC连接池以及Spring启动,JDBC模板和SQL Server。当应用程序等待数据库连接时,我需要知道连接池内部的内容。比如......

  • 有效连接数
  • 没有空闲连接
  • 阻止连接数,阻止此连接阻止的其他信息
  • 可用连接数
  • 和......

有没有办法通过调试或使用log4j等日志框架来获取这些信息?

任何想法都将受到赞赏。

3 个答案:

答案 0 :(得分:34)

经过大量研究,我能找到3种记录方式。监控数据库连接池。

https://tomcat.apache.org/tomcat-8.0-doc/jdbc-pool.html

  1. 使用 Spring Boot 属性进行监控

  2. 使用 JMX进行监控(Java管理扩展)(如@nitin建议的那样)

  3. 使用 Spring 方面进行监控。

  4. 第一种方式:使用Spring Boot属性进行监控。

    我在Spring引导属性下面找到了对于log& amp;监控数据库连接池。

    这些属性(以及更多属性)未记录。 有关详细信息,请参阅下面的github问题。 https://github.com/spring-projects/spring-boot/issues/1829

    #Maximum no.of active connections
    spring.datasource.max-active=10
    
    #Log the stack trace of abandoned connection
    spring.datasource.log-abandoned=true
    
    #Remove abandoned connection,So, new connection will be created and made available to threads which are waiting for DB connection
    spring.datasource.remove-abandoned=true
    
    #If any connection is not used for 10 seconds, consider that connection as "abandoned"
    spring.datasource.remove-abandoned-timeout=10 
    
    #Number of ms to wait before throwing an exception if no connection is available.
    spring.datasource.max-wait=1000
    

    此列表包含更多与仅数据源相关的属性。(取自上面的链接)

    spring.datasource.abandon-when-percentage-full
    spring.datasource.access-to-underlying-connection-allowed
    spring.datasource.alternate-username-allowed
    spring.datasource.auto-commit
    spring.datasource.catalog
    spring.datasource.commit-on-return
    spring.datasource.connection-customizer
    spring.datasource.connection-customizer-class-name
    spring.datasource.connection-init-sql
    spring.datasource.connection-init-sqls
    spring.datasource.connection-properties
    spring.datasource.connection-test-query
    spring.datasource.connection-timeout
    spring.datasource.data-source
    spring.datasource.data-source-class-name
    spring.datasource.data-source-j-n-d-i
    spring.datasource.data-source-properties
    spring.datasource.db-properties
    spring.datasource.default-auto-commit
    spring.datasource.default-catalog
    spring.datasource.default-read-only
    spring.datasource.default-transaction-isolation
    spring.datasource.driver-class-loader
    spring.datasource.fair-queue
    spring.datasource.idle-timeout
    spring.datasource.ignore-exception-on-pre-load
    spring.datasource.init-s-q-l
    spring.datasource.initialization-fail-fast
    spring.datasource.isolate-internal-queries
    spring.datasource.jdbc-interceptors
    spring.datasource.jdbc-url
    spring.datasource.jdbc4-connection-test
    spring.datasource.leak-detection-threshold
    spring.datasource.log-abandoned
    spring.datasource.log-validation-errors
    spring.datasource.log-writer
    spring.datasource.login-timeout
    spring.datasource.max-age
    spring.datasource.max-lifetime
    spring.datasource.max-open-prepared-statements
    spring.datasource.maximum-pool-size
    spring.datasource.metrics-tracker-class-name
    spring.datasource.minimum-idle
    spring.datasource.num-tests-per-eviction-run
    spring.datasource.pool-name
    spring.datasource.pool-prepared-statements
    spring.datasource.pool-properties
    spring.datasource.propagate-interrupt-state
    spring.datasource.read-only
    spring.datasource.record-metrics
    spring.datasource.register-mbeans
    spring.datasource.remove-abandoned
    spring.datasource.remove-abandoned-timeout
    spring.datasource.rollback-on-return
    spring.datasource.suspect-timeout
    spring.datasource.test-on-connect
    spring.datasource.thread-factory
    spring.datasource.transaction-isolation
    spring.datasource.use-disposable-connection-facade
    spring.datasource.use-equals
    spring.datasource.use-lock
    spring.datasource.validation-interval
    spring.datasource.validation-query-timeout
    spring.datasource.validator
    spring.datasource.validator-class-name
    spring.datasource.xa
    spring.datasource.xa.data-source-class-name
    spring.datasource.xa.properties
    

    第二种方式:使用JMX(Java管理扩展)进行监控

    Tomcat JDBC池提供了一个MBean,即ConnectionPoolMBean。

    https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/tomcat/jdbc/pool/jmx/ConnectionPoolMBean.html

    Spring Boot自动注册JMX MBean。因此,无需将此MBean注册/导出到MBean服务器。只需打开JDK附带的JConsole,打开,在Windows中>命令提示符 - > jconsole,就是这样。 有关详细信息,请参阅下面的屏幕截图。

    enter image description here

    enter image description here

    每当连接被放弃,连接失败,查询花费很长时间等时,此MBean也会通知。请参阅下面的截图。

    enter image description here

    第三种方式:使用Spring方面进行监控(仅适用于开发/ QA环境)。

    我使用此方面来记录TomcatJdbc连接池。

    我创建了一个Spring Aspect,它将拦截每个数据库调用。这将肯定会影响性能

    因此,在开发/ QA环境中使用此方面,在不需要时注释掉此方法(例如:在生产部署期间)。

    @Before("execution(* com.test.app.db.dao.*.*(..))")
        public void logBeforeConnection(JoinPoint jp) throws Throwable {
            String methodName = "";
            methodName += jp.getTarget().getClass().getName();
            methodName += ":";
            methodName += jp.getSignature().getName();
            logger.info("before method call : " + methodName +  " : number of connections in use by the application (active) : "+ tomcatJdbcPoolDataSource.getNumActive());
            logger.info("before method call : " + methodName +  " : the number of established but idle connections : "+ tomcatJdbcPoolDataSource.getNumIdle());
            logger.info("before method call : " + methodName +  " : number of threads waiting for a connection : "+ tomcatJdbcPoolDataSource.getWaitCount());
        }
    
    
    @After("execution(* com.test.app.db.dao.*.*(..)) ")
    public void logAfterConnection(JoinPoint jp) throws Throwable {
        String methodName = "";
        methodName += jp.getTarget().getClass().getName();
        methodName += ":";
        methodName += jp.getSignature().getName();
        logger.info("after method call : " + methodName +  " : number of connections in use by the application (active) : "+ tomcatJdbcPoolDataSource.getNumActive());
        logger.info("after method call : " + methodName +  " : the number of established but idle connections : "+ tomcatJdbcPoolDataSource.getNumIdle());
        logger.info("after method call : " + methodName +  " : number of threads waiting for a connection : "+ tomcatJdbcPoolDataSource.getWaitCount());
        //tomcatJdbcPoolDataSource.checkAbandoned();
    }
    

    现在,您可以轻松识别在您的应用程序中创建连接泄漏的特定数据库调用。

答案 1 :(得分:3)

感谢@Sundararaj Govindasamy给出了很好的答案。基于此,我在Spring Boot Application中创建了一个组件来调试我的数据库池信息。

import org.apache.tomcat.jdbc.pool.DataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspectLogger {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private DataSource ds;

    @Before("execution(* br.com.foo.core.repository.*.*(..))")
    public void logBeforeConnection(JoinPoint jp) throws Throwable {
        logDataSourceInfos("Before", jp);
    }

    @After("execution(* br.com.foo.core.repository.*.*(..)) ")
    public void logAfterConnection(JoinPoint jp) throws Throwable {
        logDataSourceInfos("After", jp);
    }

    public void logDataSourceInfos(final String time, final JoinPoint jp) {
        final String method = String.format("%s:%s", jp.getTarget().getClass().getName(), jp.getSignature().getName());
        logger.info(String.format("%s %s: number of connections in use by the application (active): %d.", time, method, ds.getNumActive()));
        logger.info(String.format("%s %s: the number of established but idle connections: %d.", time, method, ds.getNumIdle()));
        logger.info(String.format("%s %s: number of threads waiting for a connection: %d.", time, method, ds.getWaitCount()));
    }
}

答案 2 :(得分:0)

这是一个纯JSP页面MBean调试器,易于在每个Tomcat版本中使用,而无需外部依赖。调用dumpMBean.jsp?name=ConnectionPool列出dbpool或将名称保留为空以转储所有MBean。

<%@ page contentType="text/plain; charset=UTF-8" pageEncoding="ISO-8859-1"
  session="false"
  import="java.io.*, java.util.*, java.net.*,
  javax.management.*, java.lang.management.ManagementFactory
  "
%><%!

private void dumpMBean(MBeanServer server, ObjectName objName, MBeanInfo mbi, Writer writer) throws Exception {
    writer.write(String.format("MBeanClassName=%s%n", mbi.getClassName()));
    Map<String,String> props=new HashMap<String,String>();
    int idx=0;
    for(MBeanAttributeInfo mf : mbi.getAttributes()) {
        idx++;
        try {
            Object attr = server.getAttribute(objName, mf.getName());
            if (attr!=null)
                props.put(mf.getName(), attr.toString());
        } catch(Exception ex) {
            // sun.management.RuntimeImpl: java.lang.UnsupportedOperationException(Boot class path mechanism is not supported)
            props.put("error_"+idx, ex.getClass().getName()+" "+ex.getMessage());
        }
    }
    // sort by hashmap keys
    for(String sKey : new TreeSet<String>(props.keySet()))
        writer.write(String.format("%s=%s%n", sKey, props.get(sKey)));
}

%><%
// Dump MBean management properties, all beans or named beans
// dumpMBean.jsp?name=ConnectionPool,ContainerMBean
// dumpMBean.jsp?name=

if (request.getCharacterEncoding()==null)
    request.setCharacterEncoding("UTF-8");

String val = request.getParameter("name");
String[] names = val!=null ? val.trim().split(",") : new String[0];
if (names.length==1 && names[0].isEmpty()) names=new String[0];

MBeanServer server = ManagementFactory.getPlatformMBeanServer();
for(ObjectName objName : server.queryNames(null,null)) {
    MBeanInfo mbi = server.getMBeanInfo(objName);
    boolean match = names.length<1;
    String name = mbi.getClassName();
    for(int idx=0; idx<names.length; idx++) {
        if (name.endsWith(names[idx])) {
            match=true;
            break;
        }
    }
    if (match) {
        dumpMBean(server, objName, mbi, out);
        out.println("");
    }
}
out.flush();

%>