数据库连接池极简教程
一,常规数据库连接
常规数据库连接一般由以下六个步骤构成:阅读本文,建议大家已经掌握了扎实的互联网架构技术,可参考:互联网架构技术清单
装载数据库驱动程序;
建立数据库连接;
创建数据库操作对象
访问数据库,执行sql语句;
处理返回结果集
断开数据库连接。
public class TestMysqlConn { public static void main(String[] args) { Connection con; Statement stmt; ResultSet rs; try { //1,装载数据库驱动程序 Class.forName("com.mysql.jdbc.Driver").newInstance(); //2,建立数据库连接 con = DriverManager.getConnection("jdbc:mysql://3xmq.com:3306/test","root","root"); //3,创建数据库操作对象 stmt = con.createStatement(); //4,执行sql语句 rs = stmt.executeQuery("select * from _test"); //5,处理返回结果集 while(rs.next()){ int num = rs.getInt("id"); String name = rs.getString("name"); String des = rs.getString("description"); System.out.println(num + " " + name + " " + des); } //6,断开数据库连接 stmt.close(); conn.close(); } catch (Exception e) { e.printStackTrace(); System.out.println("连接失败"); } } }
二,常规数据库连接底层原理
数据库本身实际上就是一个Server端程序在跑,我们开发的程序连接数据库,相当于启动了一个Client端,连接到Server端,也就是C/S模式!那么数据库连接本质上是基于什么协议呢?以mysql连接为例,常见两种连接场景命令如下:
1,mysql -h localhost -uroot -p(本地模式)
2,mysql -h 127.0.0.1 -uroot -p(IP模式)
对场景一,使用tcpdump抓包如下:
可以看到并没有抓到网络请求数据,说明它走的是本地socket协议,unix domain socket。
对场景二,使用tcpdump进行抓包如下:
可以看到,mysql的连接过程,内部实际上是经过tcp/ip协议的,mysql上层基于tcp/ip协议封装了自己的一套消息协议!说白了,底层是基于tcp/ip socket 协议!
在mysql中使用命令:show status,可以看到mysql实际上会创建一个线程来处理客户端连接上来的连接!如下图:
Threads_connected连接数是1,mysql此时有一个连接!
Threads_created为3,说明曾经有3个connection连接过数据库!
Threads_cached为2,mysql内部的线程连接池,将空闲的连接不是立即销毁而是放到线程连接池中,如果新加进来连接不是立刻创建线程而是先从线程连接池中找到空闲的连接线程,然后分配,如果没有才创建新的线程。可见mysql内部已经为我们做优化了。
Threads_running为1,当前活跃线程数为1。
小提示:
1,Threads_catched值不是无限大的,一般为32左右。 mysql是可以调整单线程和多线程模式的,单线程只允许一个线程连接mysql,其他连接将会被拒绝。
2,数据库本地unix domain socket连接快于tcp/ip连接
三,常规数据库连接优化空间
以mysql为例,要做优化,首先要寻找数据库连接占用的资源有哪些?
1,mysql每个连接是会创建一个线程的,可以登录mysql输入show status查看Threads_connected和Threads_created的大小,那么我们每连接一次mysql就会创建一个线程,每次断开又会销毁一个线程。
数据库连接的创建和销毁本质就是线程的创建和销毁,而创建线程和销毁线程的资源消耗是非常大的。系统为每个线程分配栈空间,可以通过ulimis -s来查看,ubuntu 14.04默认是8M,那么100个连接就是800M,很吃内存的。其次mysql数据库会为每个连接分配连接缓冲区和结果缓冲区,也是要消耗时间的。
2,mysql的每次连接,都会进行tcp3次握手和断开时的4次挥手,分配一些缓存空间,也会消耗一定的时间。
如下图:
数据库连接池有效的避免了上述的问题,数据库连接池技术的思想非常简单,将数据库连接作为对象存储在一个Vector对象中,一旦数据库连接建立后,不同的数据库访问请求就可以共享这些连接,这样,通过复用这些已经建立的数据库连接,可以克服上述缺点,极大地节省系统资源和时间。
也就是我们提前创建好这些连接,然后需要用去取连接即可。和线程池的思想是一致的。
四,数据库连接池
数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患。所以,在程序初始化的时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,还更加安全可靠。
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。如下图:
数据库连接池机制:
(1)建立数据库连接池对象(服务器启动)。
(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。
(4)存取数据库。
(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。
(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。
数据库连接池在初始化时,按照连接池最小连接数,创建相应数量连接放入池中,无论是否被使用。当连接请求数大于最大连接数阀值时,会加入到等待队列!
数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.
最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放.
五,常见数据库连接池
在Java中开源的常用的数据库连接池有以下几种 :
1)DBCP
DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序中使用,Tomcat的数据源使用的就是DBCP。
2)c3p0
c3p0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
3)Druid
阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个ProxyDriver,一系列内置的JDBC组件库,一个SQL Parser。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。
Druid针对Oracle和MySql做了特别优化,比如Oracle的PS Cache内存占用优化,MySql的ping检测优化。
Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQL Parser,支持Visitor模式,使得分析SQL的抽象语法树很方便。
简单SQL语句用时10微秒以内,复杂SQL用时30微秒。
通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的WallFilter就是通过Druid的SQL Parser分析语义实现的。
六,数据库连接池配置
连接池配置大体可以分为基本配置、关键配置、性能配置等主要配置。
6.1 基本配置
基本配置是指连接池进行数据库连接的四个基本必需配置:
传递给JDBC驱动的用于连接数据库的用户名、密码、URL以及驱动类名。
DBCP | c3p0 | Druid | |
用户名 | username | user | username |
密码 | password | password | password |
URL | url | jdbcUrl | jdbcUrl |
驱动类名 | driverClassName | driverClass | driverClassName |
注:在Druid连接池的配置中,driverClassName可配可不配,如果不配置会根据url自动识别dbType(数据库类型),然后选择相应的driverClassName。
6.2 关键配置
为了发挥数据库连接池的作用,在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
最小连接数:
是数据库一直保持的数据库连接数,所以如果应用程序对数据库连接的使用量不大,将有大量的数据库资源被浪费。
初始化连接数:
连接池启动时创建的初始化数据库连接数量。
最大连接数:
是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求被加入到等待队列中。
最大等待时间:
当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置参数为0或者负数使得无限等待(根据不同连接池配置)。
DBCP | c3p0 | Druid | |
最小连接数 | minIdle(0) | minPoolSize(3) | minIdle(0) |
初始化连接数 | initialSize(0) | initialPoolSize(3) | initialSize(0) |
最大连接数 | maxTotal(8) | maxPoolSize(15) | maxActive(8) |
最大等待时间 | maxWaitMillis(毫秒) | maxIdleTime(0秒) | maxWait(毫秒) |
注1:在DBCP连接池的配置中,还有一个maxIdle的属性,表示最大空闲连接数,超过的空闲连接将被释放,默认值为8。对应的该属性在Druid连接池已不再使用,配置了也没有效果,c3p0连接池则没有对应的属性。
注2:数据库连接池在初始化的时候会创建initialSize个连接,当有数据库操作时,会从池中取出一个连接。如果当前池中正在使用的连接数等于maxActive,则会等待一段时间,等待其他操作释放掉某一个连接,如果这个等待时间超过了maxWait,则会报错;如果当前正在使用的连接数没有达到maxActive,则判断当前是否空闲连接,如果有则直接使用空闲连接,如果没有则新建立一个连接。在连接使用完毕后,不是将其物理连接关闭,而是将其放入池中等待其他操作复用。
6.3 性能配置
预缓存设置:
即是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle。JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements属于单个connection而不是整个连接池,所以设置这个参数需要考虑到多方面的因素。
单个连接拥有的最大缓存数:要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
连接有效性检测设置:
连接池内部有机制判断,如果当前的总的连接数少于miniIdle,则会建立新的空闲连接,以保证连接数得到miniIdle。如果当前连接池中某个连接在空闲了timeBetweenEvictionRunsMillis时间后任然没有使用,则被物理性的关闭掉。有些数据库连接的时候有超时限制(mysql连接在8小时后断开),或者由于网络中断等原因,连接池的连接会出现失效的情况,这时候设置一个testWhileIdle参数为true,可以保证连接池内部定时检测连接的可用性,不可用的连接会被抛弃或者重建,最大情况的保证从连接池中得到的Connection对象是可用的。当然,为了保证绝对的可用性,你也可以使用testOnBorrow为true(即在获取Connection对象时检测其可用性),不过这样会影响性能。
超时连接关闭设置:
removeAbandoned参数,用来检测到当前使用的连接是否发生了连接泄露,所以在代码内部就假定如果一个连接建立连接的时间很长,则将其认定为泄露,继而强制将其关闭掉。
作者微信:didumy,欢迎技术交流,来自:架构师小秘圈技术社区