介绍
如果不使用连接池
一般来说,Java应用程序访问数据库的过程是:
- 装载数据库驱动程序;
- 通过jdbc建立数据库连接;
- 访问数据库,执行sql语句;
- 断开数据库连接。
这样每一次请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。
其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。
数据库连接池原理
对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。
开源数据库连接池
很多流行的性能优良的第三方数据库连接池jar包供我们使用。如:
- Apache commons-dbcp
- c3p0
- BoneCP
- Druid
- HikariCP
其中c3p0已经很久没有更新了。DBCP更新速度很慢,基本处于不活跃状态,而Druid和HikariCP处于活跃状态的更新中。
HiKariCP
HiKariCP是数据库连接池的一个后起之秀,号称性能最好,可以完美地PK掉其他连接池,是一个高性能的JDBC连接池,基于BoneCP做了不少的改进和优化。
HikariCP通过优化(concurrentBag,fastStatementList )集合来提高并发的读写效率。
HikariCP使用threadlocal缓存连接及大量使用CAS的机制,最大限度的避免lock。单可能带来cpu使用率的上升。
从字节码的维度优化代码。 (default inline threshold for a JVM running the server Hotspot compiler is 35 bytecodes )让方法尽量在35个字节码一下,来提升jvm的处理效率。
同时,Spring Boot 2默认数据库连接池就是HikariCP。
自定义数据库连接池
这里技术核心在于BlockingQueue的使用。
本人在i5的机器上使用通过比较创建100个连接池与先初始化后放入BlockingQueue中进行取放100次所消耗的时间相比,量级为1000:1,经检验,通过空间换时间的方式能极大提高程序执行的效率,因为业务需求最大并发量在10W左右,所以我实际设置的池子大小为100.
我自己模拟了近百万条数据进行测试,数据插入一条不差,而且效率提升很显著。框架的功能太多,未必都能用到,适合自己的就好。
import com.cc.utils.LoggerUtil;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
/**
* Created by cc on 2017/9/21.
*/
public class MyConnection {
//包装真实的Connetion对象
private Connection connection = null;
public static Logger logger = LoggerUtil.getLogger(MyConnection.class);
//这里采用的是BlockingQueue阻塞队列作为连接池
private static BlockingQueue<MyConnection> pool ;
private static String PASSWORD;
private static String URL;
private static String DRIVER;
private static String USER;
//数据库连接池的容量
private static int POOLSIZE;
static {
Properties prop = new Properties();
/**
* 加载jdbc配置文件
* 资源文件在resource目录下:/conf/jdbc.properties
*/
InputStream in = BaseDao.class.getResourceAsStream("/conf/jdbc.properties");
try {
prop.load(in);
URL = prop.getProperty("URL").trim();
DRIVER = prop.getProperty("DRIVER").trim();
USER = prop.getProperty("USER").trim();
PASSWORD = prop.getProperty("PASSWORD").trim();
POOLSIZE = Integer.valueOf(prop.getProperty("POOLSIZE").trim());
Class.forName(DRIVER);
//创建线程池
pool = new LinkedBlockingDeque<>(POOLSIZE);
} catch (IOException e) {
logger.error("jdbc.properties加载失败");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public MyConnection() throws SQLException {
connection = DriverManager.getConnection(URL,USER,PASSWORD);
}
public MyConnection(Connection conn) {
connection = conn;
}
/**
* 向线程池添加MyConnection对象
* 返回true代表成功
* @return
*/
public boolean addConnection() {
return pool.offer(this);
}
/**
* 这里的关闭并不是真正意义上的关闭,而是将MyConnection对象返送给连接池中
* offer的用法在于在队列中添加对象,若是队列满了则返回false
* 所以这里有个判断,若是为null,则真正关闭connetion
* @throws SQLException
*/
public void close() throws SQLException {
if (!pool.offer(this)) {
connection.close();
}
}
/**
* 这里是真正意义上关闭数据库连接
* @throws SQLException
*/
public void closeConnetion() throws SQLException {
connection.close();
}
/**
* 从连接池中获取MyConnetion对象
* poll的作用在于若是连接池空了则返回null
* 所以下面有个判断若是myConnetion为空则新建连接
* 但是由于数据库有最大连接限制,大并发下显然不能无限创建connection
* 所以通过take方法进行阻塞,使得池中返还对象后立即返回
* @return
*/
public static MyConnection getConnection() {
MyConnection myConnection = pool.poll();
if (myConnection != null) {
return myConnection;
} else {
try {
myConnection =new MyConnection();
} catch (SQLException e) {
try {
myConnection = pool.take();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
return myConnection;
}
}
/**
* 阻塞式的从池中获取MyConnetion,不会新建新的对象
* 这样能保证最大连接数不变
* @return
* @throws InterruptedException
*/
public static MyConnection takeConnection() throws InterruptedException {
return pool.take();
}
//获取PreparedStatement对象
public PreparedStatement getPreparedStatement(String sql) throws SQLException {
return connection.prepareStatement(sql);
}
/**
* 这个方法是用来解决mysql数据库8小时超时的问题
* 默认一个Connection超过8小时未被使用就会被mysql挂掉
* 我的解决方案是若出现异常后我会进行捕获然后调用此方法验证是否是由于Connetion失效所致
* 若true,则新建Connection对象
* @param time
* @return
* @throws SQLException
*/
public boolean isValid(int time) throws SQLException {
return connection.isValid(time);
}
}
通过 show variables like ‘%max_connections%’; 命令可以查看mysql数据库最大连接数,通过修改my.ini文件中max_connections属性的值,连接数最大可以达到16384。