前言
一般数据库操作、主机操作等经常会涉及到会话,什么是会话,会话在日常生活中就是指两个人或者多个人直接的交流,同样的在IT行业会话一般是指客户端和服务端之间的通信交流。比如数据库,如果使用可视化界面做为客户端和数据库服务进行交互通常就会建立一个“会话”,在软件中一般称为连接。
开发者们在程序中经常也会创建客户端用来和服务端进行通信,通信完成后和服务器断开连接。如果程序中在使用到数据库的地方都创建一个连接,那么服务端必然会承受不了这么多的连接,因此产生了池的概念,池,顾名思义就是一个可以存储某种物质的容器,上述的场景中,池存储的便是数据库连接。创建出来的连接在程序A使用完成后不需要断开,只需要归还到池中即可。当程序B需要时也直接从池中获取。这样就达到了一个资源复用的效果。即节约了创建连接的时间,也缓解了数据库的压力。
连接池的实现
1、抽象连接池接口
/**
* @program: lifeguard
* @description: 连接池接口
* @author: Cheng Zhi
* @create: 2024-01-15 14:10
**/
public interface IPool<T> extends Closeable {
/**
* 获取连接
* @return
* @throws Exception
*/
public T poll();
/**
* 归还连接
* @param conn
*/
public void offer(T conn);
/**
* 查询状态
* @return
*/
public PoolStatus getStatus();
/**
* 获得数据源
* @return
*/
Object getDatasource();
/**
* 关闭多余的连接
*/
public void closeConnectionTillMin();
/**
* 关闭连接池,包括释放其中全部的连接
*/
void close();
}
2、连接池实现
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @program: lifeguard
* @description: JDBC连接池
* @author: Cheng Zhi
* @create: 2024-01-15 14:09
**/
public class JdbcConnectionPool implements IPool<JdbcConnection> {
Logger logger = LoggerFactory.getLogger(JdbcConnectionPool.class);
private DataSource datasource;
/**
* 最大连接数
*/
private int max;
/**
* 最小连接数
*/
private int min;
/**
* 被取走的连接数
*/
private AtomicInteger used = new AtomicInteger();
/**
* 空闲连接数
*/
private final BlockingQueue<JdbcConnection> freeConns;
/**
* 连接池是否被关闭
*/
private boolean isClosed = false;
public JdbcConnectionPool(int min, int max, DataSource ds) {
this.min = min;
this.max = max;
this.datasource = ds;
this.freeConns = new LinkedBlockingQueue<JdbcConnection>(max);
PoolReleaseWorker.getInstance().addPool(this);
}
@Override
public JdbcConnection poll(){
try {
JdbcConnection conn = freeConns.poll();
if(conn != null){
used.incrementAndGet(); // 计数器自增
return conn;
}
if (used.get() < max) {// 尝试用新连接
used.incrementAndGet(); // 必须立刻累加计数器,否则并发的线程会立刻抢先创建对象,从而超出连接池限制
conn = new JdbcConnection(datasource.getConnection(), this);
} else {
used.incrementAndGet(); // 提前计数,并发下为了严格阻止连接池超出上限,必须这样做
conn = freeConns.poll(5000000000L, TimeUnit.NANOSECONDS);// 5秒
if (conn == null) {
used.decrementAndGet();// 回滚计数器
throw new SQLException("No connection avaliable now." + getStatus());
}
conn = ensureOpen(conn);
}
return conn;
} catch (Exception e) {
logger.error("从连接池获取连接异常", e);
}
return null;
}
/**
* 检查连接是否可用,如果不可以则新建
* @param conn
* @return
* @throws SQLException
*/
private JdbcConnection ensureOpen(JdbcConnection conn) throws SQLException {
boolean closed = true;
try {
closed = conn.isClosed();
} catch (SQLException e) {
conn.closePhysical();
}
if (closed) {
return new JdbcConnection(datasource.getConnection(),this);
} else {
return conn;
}
}
@Override
public void offer(JdbcConnection conn) {
if (isClosed) {
// 如果连接池已经被关闭,该连接意味着无家可归,应及时关闭
conn.closePhysical();
}
boolean success = freeConns.offer(conn);
if (!success) {
// 归还连接失败,关闭物理连接
conn.closePhysical();
}
used.decrementAndGet();
}
@Override
public PoolStatus getStatus() {
int used = this.used.get();
int free = freeConns.size();
return new PoolStatus(max, min, used + free, used, free);
}
@Override
public DataSource getDatasource() {
return this.datasource;
}
/**
* 关闭多余连接,不需要人为关闭,PoolReleaseWorker会自动关闭。
*/
@Override
public void closeConnectionTillMin() {
if (freeConns.size() > min) {
JdbcConnection conn;
while (freeConns.size() > min && (conn = freeConns.poll()) != null) {
conn.closePhysical();
}
}
}
@Override
public void close(){
max = 0;
min = 0;
closeConnectionTillMin();
PoolReleaseWorker.getInstance().removePool(this);
this.isClosed = true;
}
@Override
public void closePhysical() {
this.close();
}
@Override
protected void finalize() throws Throwable {
this.close();
super.finalize();
}
}
连接池自动缩容
池,一般都会设计成可以自动根据使用需求来进行自动扩容和缩容,扩容的实现一般相对简单,比如上面的代码中当前连接不够用且没有达到指定的最大值时即可继续扩容。而缩容则相对要复杂一些,因为连接池本身并不知道什么时候是缩容的最佳时机,如果在归还连接时将立即将超出核心连接数的多余连接关闭的话,由于此时并不能确定连接够不够用,如果不够用还要继续扩容,这样的话连接池的设计就没有意义了,因此这里新开一个线程专门进行线程的收缩和资源释放。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @program: lifeguard
* @description: 负责释放大于核心连接数的连接
* @author: Cheng Zhi
* @create: 2024-01-15 17:13
**/
public class PoolReleaseWorker extends Thread {
Logger logger = LoggerFactory.getLogger(PoolReleaseWorker.class);
private static PoolReleaseWorker prw = new PoolReleaseWorker();
/**
* 保存所有的连接池,可以是jdbc连接池,也可以是ssh连接池
*/
private final ConcurrentLinkedQueue<IPool<?>> pools = new ConcurrentLinkedQueue<IPool<?>>();
private boolean alive = true;
/**
* 清除多余连接线程运行间隔时间
*/
private static final int SLEEP_TIME = 60 * 1000;
private PoolReleaseWorker() {
super("pool-release-worker");
setDaemon(true);
}
public void addPool(IPool<?> ip) {
pools.add(ip);
synchronized (this) {
if (!isAlive() && alive) {
start();
}
}
}
public void removePool(IPool<?> ip) {
pools.remove(ip);
}
public static PoolReleaseWorker getInstance() {
return prw;
}
public void close() {
this.alive = false;
}
@Override
public void run() {
ThreadUtils.doSleep(SLEEP_TIME);
try {
while (alive) {
for (IPool<?> pool : pools) {
try {
pool.closeConnectionTillMin();
} catch (Exception e) {
logger.error("release connecton pool error", e);
}
}
ThreadUtils.doSleep(SLEEP_TIME);
}
} catch (Exception e) {
logger.error("释放连接异常", e);
}
}
}
总结
连接池的核心类都贴出来了,一些简单的比如JdbcConnection这些类就没有贴出来了,这个只是对jdbc中的connection进行了一个包装,大家可以看懂即可。
这样一个连接池就设计完成了,如果扩展一下还可以支持SSH或者其他协议的连接。虽然设计可能并不是很巧妙,但是以上案例也将池的思想体现出来了,如果大家有更好的想法,也可以一起沟通。