我们的Java Web应用程序具有搜索功能,允许用户搜索大型数据库中的记录。
如果用户指定了错误的搜索参数,那么它们将以无法结束的查询结束,因为该查询需要运行几个小时。
这是一个Web应用程序,因此他们反复尝试,查询阻塞了所有资源,从而导致严重的性能问题。
如果查询运行时间太长或使用了过多的CPU,是否可以自动终止查询?
该答案适用于Apache tomcat-jdbc数据源提供程序。
首先,您需要了解PoolProperties
setRemoveAbandonedTimeout
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();
将这些方法覆盖到所有实现类
将方法 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; } }