数据库连接池
1、数据库连接池的作用在那里?为什么要使用它?
纵所周知:我们在进行数据库连接的时候,都是通过jdbc去连接数据库的,
JDBC有SUN公司编写的一套 访问数据库的规范和标准,并提供了连接数据库的协议标准,具体的实现由各个厂商实现。JDBC是一套规范接口,而驱动就是接口的实现,比如Mysql驱动、Oracle驱动,这些就是厂商的驱动,用来连接自己家的数据库。
SUN公司的人 想着写出一套可以连接天下所有数据库的api,但他们发现是个不可能完成的任务,因为每个厂商的数据库服务差异太大。所以才以提供了这个 JDBC这一套访问数据库的规范,也因此,几乎各大厂商,对数据库的连接,都基于JDBC去实现。
既然JDBC可以实现数据库的连接,为什么还要去整一个连接池来使用呢?
传统的方式使用JDBC 的操作就是:一个请求过来,Class.foname('驱动');去创建一个连接,
使用完毕后关掉连接。不控制被连接的数量。
1.这样的方式会带来什么问题?
1)在高频访问服务器的时候,过多的连接。比如有100个请求进来,就得建立100次连接。连接-断开、连接-断开,这期间都需要建立TCP连接,三次握手过程。这样的重复循环,每次连接都需要很多时间。会占用很多系统内存,这会影响效率、速度。
2)假如程序出异常,导致连接未能正常关闭,这会一直保持着连接,久而久之会导致数据库连接数量达到上限,无法后续请求无法操作数据库,会导致内存泄漏问题。
3)...
数据库连接池,就是解决这些问题的。
数据库连接池,对JDBC进行数据库的连接,进行了包装管控这些连接,使用这个连接池,需要配置数据库的最小最大连接数量。确保不让资源毫无顾忌的被分配出去。而且这些连接可复用。不管有多少个请求来,最大的连接数量也不会大于配置的最大连接数量。
数据库的连接池的作用就体现在了:资源重用,一次连接多次复用、提高效率、统一管理连接、避免资源泄漏等问题。空间换时间。
2、数据库连接池的实现原理是什么?
连接池是用于管控数据库的连接的,所以连接池可根据配置去创建多少个连接。
比如:配置了初始化连接 3个,最大连接数量 10个,每个空闲连接的时间 1分钟。
在初始化连接池的时候:就会预先创建好3个数据库连接。将这些连接放到一个链表中,当进行操作数据的时候(getConnect()),直接 从链表中取出一个连接,取操作数据库,这个时候。就少去了进行数据库连接的时间。初始化的时候已经初始化了3个连接了。(好比是 兵马未动,粮草先行的道理)。
当这个连接使用完毕后,调用close(); 连接池就又将当前的连接放回到链表中,等待下一个请求来拿。(资源重用,一次连接多次使用)。
当来的请求大于3个的时候,比如5个请求:此时链表的中的连接不够数:只有3个怎么办?
3个不够,那就继续创建连接,在创建多两个连接就够这5个请求使用了。
假如15个连接怎么办,那就继续创建连接,直到所有的连接数量达到了10个。那剩余的5个请求,就得进入队列等待一段时间,等其他请求的释放连接,才能操作数据库了。如果在一定时间内还获取不到连接,那这个请求就无法操作数据库了。
等创建的连接数量大于 3个的时候。然后很久都没有请求来的时候,这个时候就会根据配置的连接空闲时间去关闭一些连接了(释放资源)。但会保持着最小的连接。
3、根据原理可以自己实现自己的连接池吗?
理论层面的东西只是支撑,看懂了理论或许能糊弄住面试官。我们不仅要能说会道让面试官折服于你的口才,更要能动手去征服面试官。此动手非比动手。我说的是能实践动手操作能力要与你的能说会到相吻合。做程序员的我觉得代码能力比能说会道的这一点要有含金量。
其实我身边有这种人:根据自己的思想能写出某些设计模式思想的代码出来。但是你问他:卧槽你设计模式运用得好溜啊。他一脸懵逼的说:设计模式,什么是设计模式,我没有学过。
这样的人都是实践经验比较多的人,根据自己的经验和聪明的大脑也能写出好程序。然而这些23种设计模式讲得头头是道的人,让他动手去实现一个,估计也是难。他说我没有应用场景去写。 。不是没有而是动手能力弱。
程序员这一行还是最需要的是动手能力的,即使你经验丰富,将理论付诸于行动的时候,你就会发现很多坑等着你。所以经验也是这么来的。
4、代码实现一个高并发的连接池。
造一个轮子,主要是通过实践来学习所掌握的知识点的应用,加固加深印象,提高自己的动手能力。检测自己的理解程度。
1、先看看代码结构
1、DataBaseConnectPool 连接池的接口
主要有三个简单的方法
- void init(final PoolConfig config);
- JdbcConnect getConnection();
- void recovery(Connection connection);
/**
* <br>
* 数据库连接池接口
*
* @author 永健
* @since 2020-08-30 15:50
*/
public interface DataBaseConnectPool
{
/**
* 初始化连接池
*/
void init(final PoolConfig config);
/**
* 从连接池中获取一个连接
*
* @return 连接对象
*/
JdbcConnect getConnection();
/**
* 连接池回收
*
* @param connection 连接对象
*/
void recovery(Connection connection);
}
2、ConnectPools 连接池的实现
- 线程的活跃数量
- 线程池的连接数量
- 连接对象的监控
- 单例线程池
/**
* <br>
* 连接池的实现
*
* @author 永健
* @since 2020-08-30 15:49
*/
public class ConnectPools implements DataBaseConnectPool
{
/**
* 池子
*/
private ArrayBlockingQueue<Connection> POOLS;
/**
* 使用中的连接
*/
private ArrayBlockingQueue<Connection> ACTIVES;
/**
* 连接的空闲时间记录
*/
private Map<Connection, Long> ACTIVE_MAP;
/**
* 配置
*/
private PoolConfig config;
private static int init;
private static ConnectPools connectPools;
private ConnectPools()
{
}
public static ConnectPools getInstance()
{
synchronized (ConnectPools.class)
{
if (connectPools == null)
{
System.out.println("线程池未初始化");
connectPools = new ConnectPools();
return null;
}
}
return connectPools;
}
/**
* 初始化连接池
*/
@Override
public void init(final PoolConfig config)
{
synchronized (ConnectPools.class)
{
if (init == -1)
{
return;
}
this.config = config;
int maxActive = config.getMaxActive();
int initialSize = config.getInitialSize();
if (maxActive < initialSize)
{
throw new RuntimeException("initialSize is gt maxActive");
}
POOLS = new ArrayBlockingQueue<>(maxActive);
ACTIVES = new ArrayBlockingQueue<>(maxActive);
ACTIVE_MAP = new ConcurrentHashMap<>(maxActive);
for (int i = 0; i < initialSize; i++)
{
POOLS.offer(createConnection());
}
System.out.println("初始化连接:" + initialSize + " /个");
System.out.println("最大连接:" + maxActive + " /个");
System.out.println("等待超时:" + config.getMaxWait() + " ms");
init = -1;
monitorConnectActive();
}
}
/**
* 从连接池中获取一个连接
*
* @return
*/
@Override
public JdbcConnect getConnection()
{
synchronized (this)
{
int activeSize = ACTIVES.size();
long waiteTime = config.getMaxWait();
long currentTimeMillis = System.currentTimeMillis();
// 连接池已满,进行等待
while (activeSize >= config.getMaxActive())
{
if (waiteTime == 0)
{
}
else if ((System.currentTimeMillis() - currentTimeMillis) >= waiteTime)
{
System.out.println(Thread.currentThread().getName() + " - 获取连接超时");
return null;
}
}
// 连接数量未达到最大值,创建新的连接
Connection connection = null;
if (POOLS.isEmpty())
{
connection = createConnection();
}
else
{
// 从连接池中获取
connection = POOLS.poll();
}
ACTIVE_MAP.put(connection, System.currentTimeMillis());
ACTIVES.offer(connection);
return new JdbcConnect(connection);
}
}
/**
* 连接回收
*
* @param connection 连接对象
*/
@Override
public void recovery(Connection connection)
{
synchronized (this)
{
try
{
if (connection == null || connection.isClosed() || !connection.isValid(2))
{
connection = createConnection();
}
ACTIVES.remove(connection);
POOLS.offer(connection);
} catch (SQLException e)
{
e.printStackTrace();
}
System.out.println();
System.out.println("close");
System.out.println("POOLS size():" + POOLS.size());
System.out.println("ACTIVES size():" + ACTIVES.size());
System.out.println();
}
}
/**
* 创建数据库连接
*
* @return Connection
*/
private Connection createConnection()
{
if (ACTIVES.size() >= config.getMaxActive())
{
throw new RuntimeException("不能创建连接,连接数量已满");
}
Connection connection = null;
try
{
Class.forName(config.getDriverClassName());
connection = DriverManager.getConnection(config.getJdbcUrl(), config.getUsername(), config.getPassword());
System.out.println("----------- @_@! 新建数据库连接成功 -----------");
} catch (Exception e)
{
e.printStackTrace();
System.out.println("@_@! 数据库连接失败");
}
return connection;
}
/**
* 监控连接池中的连接
* 判断是否有连接空闲时间超过了 配置的空闲时间
* 超过的将其释放断开连接
*/
private void monitorConnectActive()
{
Set<Map.Entry<Connection, Long>> entries = ACTIVE_MAP.entrySet();
new Thread(() -> {
while (true)
{
try
{
// 多久检测一次
Thread.sleep(config.getTimeBetweenEvictionRunsMillis());
} catch (InterruptedException e)
{
e.printStackTrace();
}
if (POOLS.size() > config.getInitialSize())
{
entries.forEach(map -> {
long value = map.getValue();
Connection connection = map.getKey();
long minEvictableIdleTimeMillis = config.getMinEvictableIdleTimeMillis();
if (System.currentTimeMillis() - value >= minEvictableIdleTimeMillis)
{
System.out.println("该连接" + connection + "在 " + minEvictableIdleTimeMillis + "ms 内无连接,将该连接关闭,释放资源");
ACTIVE_MAP.remove(connection);
if (POOLS.size() > config.getInitialSize())
{
POOLS.remove(connection);
ACTIVES.remove(connection);
}
System.out.println("POOLS size():" + POOLS.size());
}
});
}
}
}).start();
}
}
3、JdbcConnect 包装一个Connect对象
/**
* <br>
*
* @author 永健
* @since 2020-05-20 11:42
*/
public class JdbcConnect
{
private Connection connection;
private LinkedList<Statement> STA = new LinkedList<>();
private LinkedList<ResultSet> RE = new LinkedList<>();
private boolean isUse;
public JdbcConnect(Connection connection)
{
this.isUse = true;
this.connection = connection;
}
/**
* 执行回收后 不允许操作
*/
public void isUse()
{
if (!isUse)
{
throw new RuntimeException("this connect is close");
}
}
public Connection getConnection()
{
return connection;
}
/**
* 改操作
*
* @param sql 语句
* @return 影响行数
* @throws SQLException 异常
*/
public int executeUpdate(String sql) throws SQLException
{
isUse();
int resultSet;
PreparedStatement preparedStatement = null;
try
{
System.out.println();
System.out.println("#### update sql--> ," + sql);
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeUpdate();
} catch (SQLException e)
{
throw new SQLException();
} finally
{
preparedStatement.close();
}
System.out.println();
return resultSet;
}
/**
* 执行查的sql
*
* @param sql sql语句
* @return ResultSet
*/
public ResultSet executeQuery(String sql)
{
isUse();
ResultSet resultSet = null;
Statement statement = null;
try
{
System.out.println();
connection.setAutoCommit(true);
//System.out.println("#### select sql--> " + sql);
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
STA.add(statement);
RE.addFirst(resultSet);
//System.out.println();
} catch (SQLException e)
{
e.printStackTrace();
}
return resultSet;
}
/**
* 关闭连接
*/
public void close()
{
this.isUse = false;
Stream.iterate(0, i -> i + 1).limit(RE.size()).forEach(i -> {
ResultSet resultSet = RE.get(i);
Statement statement = STA.get(i);
try
{
if (resultSet != null)
{
resultSet.close();
}
if (statement != null)
{
statement.close();
}
} catch (SQLException e)
{
e.printStackTrace();
}
});
RE.clear();
STA.clear();
ConnectPools.getInstance().recovery(connection);
}
/**
* 事物提交
*/
public void commit()
{
isUse();
try
{
connection.commit();
} catch (SQLException throwable)
{
throwable.printStackTrace();
}
}
/**
* 食物回滚
*/
public void rollback()
{
isUse();
try
{
connection.rollback();
} catch (SQLException throwable)
{
throwable.printStackTrace();
}
}
}
4、PoolConfig 连接池配置对象
/**
* <br>
* 连接池配置文件
*
* @author 永健
* @since 2020-08-30 15:52
*/
public class PoolConfig
{
/**
* 默认最小初始化连接
*/
private final static int INIT_SIZE = 1;
/**
* 默认最大连接
*/
private final static int MAX_ACTIVE = 5;
/**
* 最小空闲时间
*/
private final static int MIN_FREE_TIME = 10000;
/**
* 多久检测一次空闲的连接
*/
private final static int CHECK_TIME = 3000;
/**
* 0 的时候无时间限制
*/
private final static int MAX_WAITE = 10000;
private final static String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
private String jdbcUrl;
private String username;
private String password;
private String driverClassName = DRIVER_CLASS_NAME;
private long maxWait = MAX_WAITE;
private int initialSize = INIT_SIZE;
private int maxActive = MAX_ACTIVE;
private long minEvictableIdleTimeMillis = MIN_FREE_TIME;
private long timeBetweenEvictionRunsMillis = CHECK_TIME;
// ....... 省掉get/set
}
5、PoolTest 测试
/**
* <br>
*
* @author 永健
* @since 2020-08-30 16:25
*/
public class PoolsTest
{
private static Set<String> CONNECTS = new HashSet<>();
private static CountDownLatch latch = new CountDownLatch(50);
@Test
public void test() throws IOException
{
ConnectPools connectPools = ConnectPools.getInstance();
PoolConfig poolConfig = new PoolConfig()
.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/kfc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true")
.setUsername("root")
.setPassword("tiger")
.setInitialSize(3)
.setMaxWait(100)
.setMaxActive(10);
connectPools.init(poolConfig);
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++)
{
executorService.submit(() -> {
select(connectPools);
latch.countDown();
});
}
try
{
latch.await();
} catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("CONNECTS:" + CONNECTS.size());
new BufferedReader(new InputStreamReader(System.in)).readLine();
}
private void select(ConnectPools connectPools)
{
JdbcConnect connection = connectPools.getConnection();
if (connection == null)
{
return;
}
CONNECTS.add(connection.getConnection() + "");
ResultSet resultSet = null;
try
{
resultSet = connection.executeQuery("select * from tb_user limit 1");
handler(resultSet);
while (resultSet.next())
{
User user = new User();
for (String coulmName : FIELD_MAP.keySet())
{
Object val = resultSet.getObject(coulmName);
Field field = FIELD_MAP.get(coulmName);
if (field != null)
{
field.setAccessible(true);
try
{
field.set(user, val);
} catch (IllegalAccessException e)
{
e.printStackTrace();
}
}
}
// System.out.println(user);
}
System.out.println(Thread.currentThread().getName() + "-" + connection.getConnection());
} catch (SQLException e)
{
e.printStackTrace();
} finally
{
connection.close();
}
}
private static final List<String> COLUMN_NABS = new ArrayList<>();
private static final HashMap<String, Field> FIELD_MAP = new HashMap<>();
private void handler(ResultSet resultSet)
{
if (!FIELD_MAP.isEmpty())
{
return;
}
Class<User> personnelClass = User.class;
Field[] declaredFields = personnelClass.getDeclaredFields();
try
{
int columnCount = resultSet.getMetaData().getColumnCount();
for (int i = 0; i < columnCount; i++)
{
String columnName = resultSet.getMetaData().getColumnName(i + 1);
COLUMN_NABS.add(columnName);
for (Field field : declaredFields)
{
TableField annotation = field.getAnnotation(TableField.class);
if (annotation != null && !annotation.exist())
{
continue;
}
if (columnName.equals(field.getName()))
{
FIELD_MAP.put(field.getName(), field);
}
}
}
} catch (SQLException throwables)
{
throwables.printStackTrace();
} finally
{
}
}
}
class User
{
private int id;
private String name;
private String phone;
private String sex;
public int getId()
{
return id;
}
public User setId(int id)
{
this.id = id;
return this;
}
public String getName()
{
return name;
}
public User setName(String name)
{
this.name = name;
return this;
}
public String getPhone()
{
return phone;
}
public User setPhone(String phone)
{
this.phone = phone;
return this;
}
public String getSex()
{
return sex;
}
public User setSex(String sex)
{
this.sex = sex;
return this;
}
@Override
public String toString()
{
return "User{" +
"id=" + id +
", name='" + name + ''' +
", phone='" + phone + ''' +
", sex='" + sex + ''' +
'}';
}
}
- test()方法
模拟了50个并发请求,最终打印结果,最终只创建了10个连接对象。应为配置的时候只配置了最大连接数为10。所以不回出现,超过10个的连接数。其余线程超时未获取到连接。
连接的时候先拿到的是,初始化的三个连接。然后和不够连接继续创建连接直到10个连接。然后有一部分在等待的时间内没有获取到连接出现获取超时,接着有一部连接释放回收后,又有一部分的线程拿到了连接,仔细看这些连接是重复的。这说明这些连接是复用的。线程 48 那部分和 线程2 那部分比较,是复用的连接,虽然有50个请求但是只有20个拿到连接,其余部分无法获取得到。
其次:在所有线程跑完之后。所有线程都被回收到线程池中。
我们看 ConnectPools 中的连接监控的方法monitorConnectActive()
根据配置多久进行一次连接的空闲监控。当该连接空闲时时间超过 配置的指定时间 minEvictableIdleTimeMillis
,将其连接断开释放资源。如打印结果所示,最终释放掉7个连接对象,保持着最小的连接数量。
建议大家,在实际的项目中,尽量不要用自己造的轮子去使用,因为你的轮子没有经过各种的风吹雨打,各种环境的压测等等测试。而且还有好多优秀的框架使用。造轮子,只是来辅助我们学习的一种方式,理论结合实践才能理解的够深刻。