Java Web 下彻底解决MySQL 8 小时问题

时间:2022-11-12 22:02:52

一、问题的提出


在MySQL数据库中,当一个客户端的连接超过8小时没有向服务器发起任何请求时,连接将被服务端关闭。

而在Java Web中,很多是信息管理性质的系统,工作人员下班后服务器基本上不再向数据库发起请求,因此经常出现8小时后服务器会莫名其妙“死”一阵子的问题


二、解决思路

思路很简单,就是让 Java Web 服务器定向服务器发起一些请求,让这些连接不至于连续8小时都没事干。简而言之,就是给这些数据库连接找些事干。

通常我是让它执行 Select Now() 语句,不用访问数据库磁盘

思路虽然简单,但是实现起来却不是那么简单

1、你得保证连接池中的连接在一定时间范围内被激活一次,当然如果这个连接正在执行访问数据库的操作,则可以不激活

2、为了达到1的目的,就必须使用一个锁机制,确保连接池中所有的连接都被激活一次之后,再把这些连接放回到连接池。否则你连续取到的连接,可能是同一个。

如,你的连接池中有10个连接,如果执行10次循环,每次循环到连接池中取一次连接执行 select now() 查询,然后释放连接,那么你这10次循环可能拿到的都是同一个连接

3、如果为了避免2中的情况发生,把所有的10次连接全部占用完成之后再释放,则有可能出现死锁。


三、程序原理

创建与连接池数量相同的线程,每过一段时间(如1小时)激活这10个线程,每个线程执行一次数据库操作,然后等待2秒,最后进行统一释放连接

这样做,当系统的连接都很空闲时,可保证每个连接都被执行一次;当系统比较忙时,由于每个线程只保持2秒连接,也不会影响系统性能

使用时,在 servlet 中添加一个 ServletContextListener,启动时执行:

	public void contextDestroyed(ServletContextEvent sce) {
dbHolder.stopHold();
}

public void contextInitialized(ServletContextEvent sce) {
dbHolder = new DBHolder(
new DBHolderAction(){
private Connection conn = null;
@Override
public void doAction() throws SQLException {
conn = ....
// 执行 sql 语句
}

@Override
public void closeDBSession() throws SQLException {
try{
conn.close();
}catch(Exception ex){
}finally{
conn = null;
}
}
},
10, // 最大连接数,请从配置文件中读取,这里取固定值
60 * 60 // 运行周期
);

dbHolder.startHold();
}



定义的程序代码:

import java.util.*;

import org.apache.log4j.Logger;

public class DBHolder {

private static final Logger logger = Logger.getLogger(DBHolder.class);

private boolean holding = false;
private Object holdLock = new Object();

private int executePeriod;
private int maxActive;
private DBHolderAction action = null;

private Queue<ExecuteThread> threads = new LinkedList<ExecuteThread>();

class ExecuteThread extends Thread {

private Object locker = new Object();

public void run(){
if(action == null)
return;

synchronized(locker){
try{
long time1 = System.currentTimeMillis();
action.doAction();
long time2 = System.currentTimeMillis();
logger.debug("DBHolder:线程 " + this.getName() + " 访问数据库,共用时" + (time2 - time1) + "毫秒");
}catch(Exception e){
logger.debug(e.getMessage(), e);
}finally{

synchronized(threads){
threads.add(this);
threads.notifyAll();
}

try{
// 一个连接最多保持2秒
locker.wait(2 * 1000);
}catch(Exception e){}

try{
logger.debug("DBHolder:线程,开始关闭线程 " + this.getName() + " 的数据库资源");
action.closeDBSession();
logger.debug("DBHolder:线程,线程 " + this.getName() + " 的数据库资源已经关闭");
}catch(Exception e){
}
}
}
}

public void close(){
synchronized(locker){
try{
locker.notifyAll();
}catch(Exception ex){
}
}
}

public ExecuteThread(String name){
super(name);
}
}

public void startHold(){
if(holding == true)
return;

holding = true;

new Thread(){

@Override
public void run(){
while(holding){

logger.debug("DBHolder:调度,清理线程");
synchronized(threads){
while(threads.size() > 0){
threads.poll().close();
}
}
logger.debug("DBHolder:调度,清理线程完成,现在创建新的线程,线程数:" + maxActive);

for(int i=0; i<maxActive; i++){
new ExecuteThread("DBHolder Thread " + (i + 1)).start();
}

logger.debug("DBHolder:调度,线程启动完成,等待所有线程全部完成数据库访问");
synchronized(threads){
while(threads.size() < maxActive){
try {
logger.debug("DBHodler:调度,等待所有线程全部完成数据库访问");
threads.wait(1000);
} catch (InterruptedException e) {
}
}
}
logger.debug("DBHolder:调度,全部线程完成数据库访问,开始释放全部线程的数据库资源");

while(threads.size() > 0){
threads.poll().close();
}
logger.debug("DBHolder:调度,全部线程的数据库资源释放完毕");

synchronized(holdLock){
try{
holdLock.wait(executePeriod * 1000L);
}catch(Exception ex){}
}
}
}
}.start();
}

public void stopHold(){
try{
this.holding = false;
this.holdLock.notifyAll();
}catch(Exception ex){
}
}

/**
* 类构造器
* @param action 用于执行数据库访问的接口
* @param maxActive 数据库最大连接数
* @param executePeriod 数据库保持连接操作的运行周期,以秒为单
*/
public DBHolder(
DBHolderAction action,
int maxActive,
int executePeriod){

this.executePeriod = executePeriod;
this.maxActive = maxActive;
this.action = action;
}
}


public interface DBHolderAction {

public void doAction() throws java.sql.SQLException;

public void closeDBSession() throws java.sql.SQLException;
}