一尘不染

自动终止长时间运行的查询(MySql),Apache Tomcat数据源

tomcat

我们的Java Web应用程序具有搜索功能,允许用户搜索大型数据库中的记录。

如果用户指定了错误的搜索参数,那么它们将以无法结束的查询结束,因为该查询需要运行几个小时。

这是一个Web应用程序,因此他们反复尝试,查询阻塞了所有资源,从而导致严重的性能问题。

如果查询运行时间太长或使用了过多的CPU,是否可以自动终止查询?


阅读 238

收藏
2020-06-16

共1个答案

一尘不染

该答案适用于Apache tomcat-jdbc数据源提供程序。

首先,您需要了解PoolProperties

  1. setRemoveAbandonedTimeout

  2. setRemoveAbandoned

当查询花费的时间超过setRemoveAbandonedTimeout(int)中指定的时间时,执行此查询的连接将标记为Abandon,并调用java.sql.Connection.close()方法,它将在释放连接之前一直等待查询完成。

我们可以实现自己的处理程序来处理废弃的连接。以下是变化

首先我们需要添加一个接口

package org.apache.tomcat.jdbc.pool;

public interface AbandonedConnectionHandler {

        public void handleQuery(Long connectionId);

}

tomcat-jdbc文件更改:

PoolConfiguration.java (接口)

添加getter和setter方法。

public void setAbandonedConnectionHandler(AbandonedConnectionHandler  abandonedConnectionHandler);

public AbandonedConnectionHandler getAbandonedConnectionHandler();

将这些方法覆盖到所有实现类

  • DataSourceProxy.java
  • PoolProperties.java
  • org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.java

将方法 getConnectionId() 添加到
org.apache.tomcat.jdbc.pool.PooledConnection.java

public Long getConnectionId() {
    try {
        //jdbc impl has getId()
        Method method = this.connection.getClass().getSuperclass().getMethod("getId");
        return (Long)method.invoke(this.connection);
    } catch (Exception e) {
        log.warn(" Abandoned QueryHandler failed to initialize connection id ");
    }
    return null;
}

在使用不同的mysql驱动程序的情况下,上述反射代码可能会有所不同。

现在我们需要在org.apache.tomcat.jdbc.pool.ConnectionPool.java中调用java.sql.Connection.close()方法之前放置处理程序。

将启动废弃的连接清除程序的ConnectionPool.java方法是

protected void abandon(PooledConnection con)

在调用 release(con) 之前,在此方法内添加以下代码

if(getPoolProperties().getAbandonedConnectionHandler() != null)
            {
                con.lock();
                getPoolProperties().getAbandonedConnectionHandler().handleQuery(con.getConnectionId());
            }

现在,您要做的就是在创建tomcat-jdbc数据源时将handerInstance与PoolProperties一起传递。

p.setAbandonedConnectionHandler(new ConnectionHandler(true));

这是我的AbandonedConnectionHandler实现。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.AbandonedConnectionHandler;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;


public class ConnectionHandler implements AbandonedConnectionHandler{

    private static final Log log = LogFactory.getLog(ConnectionHandler.class);

    private Boolean isAllowedToKill;    
    private PoolConfiguration poolProperties;

    public ConnectionHandler(Boolean isAllowedToKill)
    {
        this.isAllowedToKill = isAllowedToKill;
    }

    @Override
    public void handleQuery(Long connectionId) {
        Connection conn = null;
        Statement stmt = null;
        if(this.isAllowedToKill)
        {
            try{

                Class.forName(poolProperties.getDriverClassName());
                conn = DriverManager.getConnection(poolProperties.getUrl(),poolProperties.getUsername(),poolProperties.getPassword());

                Statement statement = conn.createStatement();
                ResultSet result = statement.executeQuery("SELECT ID, INFO, USER, TIME FROM information_schema.PROCESSLIST WHERE ID=" + connectionId);

                if(result.next())
                {   
                    if(isFetchQuery(result.getString(2)))
                    {
                        statement.execute("Kill "+connectionId);
                    }

                }
                statement.close();
                conn.close();
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
            finally
            {
                try {
                    if(stmt != null && !stmt.isClosed())
                    stmt.close();
                } catch (SQLException e) {
                    log.warn("Exception while closing Statement ");
                }
                try {
                    if(conn != null && !conn.isClosed() )
                    conn.close();
                } catch (SQLException e) {
                    log.warn("Exception while closing Connection ");
                }
            }
        }
    }
    private Boolean isFetchQuery(String query)
    {
        if(query == null)
        {
            return true;
        }
        query = query.trim();
        return "SELECT".equalsIgnoreCase(query.substring(0, query.indexOf(' '))); 
    }

    public PoolConfiguration getPoolProperties() {
        return poolProperties;
    }
    public void setPoolProperties(PoolConfiguration poolProperties) {
        this.poolProperties = poolProperties;
    }

}
2020-06-16