Druid首先是一个数据库连接池,并且是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。但它不仅仅是一个数据库连接池,它还包含一个ProxyDriver,一系列内置的JDBC组件库,一个SQL Parser。
Druid支持所有JDBC兼容的数据库,包括Oracle、MySQL、Derby、Postgresql、SQL Server、H2等等,并且Druid针对Oracle和MySql做了特别优化,比如Oracle的PS Cache内存占用优化,MySql的ping检测优化。
通过Druid提供的监控功能,监控SQL的执行时间、ResultSet持有时间、返回行数、更新行数、错误次数、错误堆栈信息,可以清楚知道连接池和SQL的工作情况,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
com.alibaba
druid
1.0.20
classpath:druid.properties
url=jdbc:mysql://localhost:3306/era
username=root
password=123456
#初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
initialSize =1
#定义最大连接池数量
maxActive=20
#获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
maxWait=60000
#是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。
#在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。5.5及以上版本有PSCache,建议开启。
poolPreparedStatements=false
#要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
#在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
maxPoolPreparedStatementPerConnectionSize=100
#用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
validationQuery=SELECT 'x'
#申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnBorrow=false
#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn=false
#建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
testWhileIdle=true
#属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat;日志用的filter:log4j;防御sql注入的filter:wall
filters=stat,wall
#有两个含义:1) Destroy线程会检测连接的间隔时间;2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
timeBetweenEvictionRunsMillis=3000
#配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis=300000
如果要使用Druid的内置监控功能,需要配置数据源时加上
还需要在web.xml中加上:
DruidStatView
com.alibaba.druid.support.http.StatViewServlet
resetEnable
true
loginUsername
druid
loginPassword
123456
DruidStatView
/druid/*
启动项目后在浏览器输入:
http://ip:port/项目名/druid/或http://ip:port/项目名/druid/index.html即可访问。
程序部署在生产环境(数据库是oracle),大概半个小时不用,再次使用的时候就出现查询数据库卡主,停顿大概15分钟左右,15分钟之后程序抛出异常,然后再使用就可以了,多刷新几次,跳过当前有问题的那个数据库连接也能正常使用,最后解决的方法是添加了一个参数,参数名字是:keepAlive
github druid的上对这个参数的说明
https://github.com/alibaba/druid/wiki/KeepAlive_cn
druidDataSource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=2000;oracle.jdbc.ReadTimeout=30000");
//后面的ReadTimeout单位也是毫秒,不要设置的太短,否则有些sql执行时间本来就长,报错会影响正常使用。mysql可以这样写:
druidDataSource.setConnectionProperties("connectTimeout=2000;socketTimeout=30000);
sqlserver或postgresql可以这写:
druidDataSource.setConnectionProperties("loginTimeout=2000;socketTimeout=30000);
druidDataSource.setMinEvictableIdleTimeMillis(180000);
//配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
druidDataSource.setKeepAlive(true);
//打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select 1 from dual,其他数据库一般为select 1,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
这里先来介绍下DruidDataSource这个类:
DruidDataSource的UML图
图中我只列出了几个重要的属性,这几个属性没有理解好,后面的源码很难看得进去。
DruidDataSource的初始化时机是可选的,当我们设置init=true时,在createDataSource时就会调用DataSource.init()方法进行初始化,否则,只会在getConnection时再进行初始化。数据源初始化主要逻辑在DataSource.init()这个方法,可以概括为以下步骤:
这个方法差不多200行,考虑篇幅,我删减了部分内容。
druid数据源初始化采用的是ReentrantLock,如下:
注意,以下步骤均在这个锁的范围内。
这部分内容主要是初始化一些属性,需要注意的一点就是,这里使用了AtomicLongFieldUpdater来进行原子更新,保证写的安全和读的高效,当然,还是cocurrent包的工具。
看到下面的代码会发现,我们还可以通过SPI机制来配置过滤器。
使用SPI配置过滤器时需要注意,对应的类需要加上@AutoLoad注解,另外还需要配置load.spifilter.skip=false,SPI相关内容可参考我的另一篇博客:使用SPI解耦你的实现类。
在这个方法里,主要就是初始化过滤器的一些属性而已。过滤器的部分,本文不会涉及到太多。
这里只是简单的校验,不涉及太多复杂的逻辑。
初始化ExceptionSorter、ValidConnectionChecker、JdbcDataSourceStat
这里重点关注ExceptionSorter和ValidConnectionChecker这两个类,这里会根据数据库类型进行选择。其中,ValidConnectionChecker用于对连接进行检测。
这里有两种方式创建连接,一种是异步,一种是同步。但是,根据我们的使用例子,createScheduler为null,所以采用的是同步的方式。
注意,后面的所有代码也是基于createScheduler为null来分析的。
创建logStatsThread、createConnectionThread和destroyConnectionThread
这里会启动三个线程。
这里使用了CountDownLatch,保证当createConnectionThread和destroyConnectionThread开始run时再继续执行。
private final CountDownLatch initedLatch = new CountDownLatch(2);
// 线程进入等待,等待CreatorThread和DestroyThread执行
initedLatch.await();
我们进入到
DruidDataSource.CreateConnectionThread.run(),可以看到,一执行run方法就会调用countDown。destroyConnectionThread也是一样,这里就不放进来了。
接下来是注册MBean,会去注册
DruidDataSourceStatManager和DruidDataSource,启动我们的程度,通过jconsole就可以看到这两个MBean。JMX相关内容这里就不多扩展了,感兴趣的话可参考我的另一篇博客: 如何使用JMX来管理程序?
// 注册MBean,用于支持JMX
registerMbean();
前面已经讲过,当我们调用empty.signal(),会去唤醒处于empty.await()状态的CreateConnectionThread。CreateConnectionThread这个线只有在需要创建连接时才运行,否则会一直等待,后面会讲到。
连接对象的获取
用户调用
DruidDataSource.getConnection,拿到的对象是DruidPooledConnection,里面封装了DruidConnectionHolder,而这个对象包含了原生的连接对象和我们一开始创建的数据源对象。
DruidPooledConnection的UML图
连接对象的获取过程可以概括为以下步骤:
初始化数据源的前面已经讲过了,这里就直接从第二步开始。
进入
DruidDataSource.getConnectionInternal方法。除了获取连接对象,其他的大部分是校验和计数的内容。
下面再看下DruidDataSource.takeLast()方法(即没有配置maxWait时调用的方法)。该方法中,当没有空闲连接对象时,会尝试创建连接,此时该线程进入等待(notEmpty.await()),只有连接对象创建完成或池中回收了连接对象(notEmpty.signal()),该线程才会继续执行。
前面已经讲到,创建连接是采用异步方式,进入到
DruidDataSource.CreateConnectionThread.run()。当不需要创建连接时,该线程进入empty.await()状态,此时需要用户线程调用empty.signal()来唤醒。
进入
DruidDataSource.getConnectionDirect(long)。该方法会使用到validConnectionChecker来校验连接的有效性。
进入
DruidDataSource.getConnectionDirect(long),这里不会进行检测,只是将连接对象放入activeConnections,具体泄露连接的检测工作是在DestroyConnectionThread线程中进行。
DestroyConnectionThread线程会根据我们设置的
timeBetweenEvictionRunsMillis来进行检验,具体的校验会去运行DestroyTask(DruidDataSource的内部类),这里看下DestroyTask的run方法。
进入
DruidDataSource.removeAbandoned(),当连接对象使用时间超过removeAbandonedTimeoutMillis,则会被丢弃掉。
进入
DruidDataSource.getConnection。
进入到
FilterChainImpl.dataSource_connect。
这里以
StatFilter.dataSource_getConnection为例。
以上,druid的源码基本已经分析完,其他部分内容有空再做补充。
这里先来介绍下DruidDataSource这个类:
DruidDataSource的UML图
图中我只列出了几个重要的属性,这几个属性没有理解好,后面的源码很难看得进去。
DruidDataSource的初始化时机是可选的,当我们设置init=true时,在createDataSource时就会调用DataSource.init()方法进行初始化,否则,只会在getConnection时再进行初始化。数据源初始化主要逻辑在DataSource.init()这个方法,可以概括为以下步骤:
这个方法差不多200行,考虑篇幅,我删减了部分内容。
druid数据源初始化采用的是ReentrantLock,如下:
注意,以下步骤均在这个锁的范围内。
这部分内容主要是初始化一些属性,需要注意的一点就是,这里使用了AtomicLongFieldUpdater来进行原子更新,保证写的安全和读的高效,当然,还是cocurrent包的工具。
看到下面的代码会发现,我们还可以通过SPI机制来配置过滤器。
使用SPI配置过滤器时需要注意,对应的类需要加上@AutoLoad注解,另外还需要配置load.spifilter.skip=false,SPI相关内容可参考我的另一篇博客:使用SPI解耦你的实现类。
在这个方法里,主要就是初始化过滤器的一些属性而已。过滤器的部分,本文不会涉及到太多。
这里只是简单的校验,不涉及太多复杂的逻辑。
初始化ExceptionSorter、ValidConnectionChecker、JdbcDataSourceStat
这里重点关注ExceptionSorter和ValidConnectionChecker这两个类,这里会根据数据库类型进行选择。其中,ValidConnectionChecker用于对连接进行检测。
这里有两种方式创建连接,一种是异步,一种是同步。但是,根据我们的使用例子,createScheduler为null,所以采用的是同步的方式。
注意,后面的所有代码也是基于createScheduler为null来分析的。
创建logStatsThread、createConnectionThread和destroyConnectionThread
这里会启动三个线程。
这里使用了CountDownLatch,保证当createConnectionThread和destroyConnectionThread开始run时再继续执行。
private final CountDownLatch initedLatch = new CountDownLatch(2);
// 线程进入等待,等待CreatorThread和DestroyThread执行
initedLatch.await();
我们进入到
DruidDataSource.CreateConnectionThread.run(),可以看到,一执行run方法就会调用countDown。destroyConnectionThread也是一样,这里就不放进来了。
接下来是注册MBean,会去注册
DruidDataSourceStatManager和DruidDataSource,启动我们的程度,通过jconsole就可以看到这两个MBean。JMX相关内容这里就不多扩展了,感兴趣的话可参考我的另一篇博客: 如何使用JMX来管理程序?
// 注册MBean,用于支持JMX
registerMbean();
前面已经讲过,当我们调用empty.signal(),会去唤醒处于empty.await()状态的CreateConnectionThread。CreateConnectionThread这个线只有在需要创建连接时才运行,否则会一直等待,后面会讲到。
连接对象的获取
用户调用
DruidDataSource.getConnection,拿到的对象是DruidPooledConnection,里面封装了DruidConnectionHolder,而这个对象包含了原生的连接对象和我们一开始创建的数据源对象。
DruidPooledConnection的UML图
连接对象的获取过程可以概括为以下步骤:
初始化数据源的前面已经讲过了,这里就直接从第二步开始。
进入
DruidDataSource.getConnectionInternal方法。除了获取连接对象,其他的大部分是校验和计数的内容。
下面再看下DruidDataSource.takeLast()方法(即没有配置maxWait时调用的方法)。该方法中,当没有空闲连接对象时,会尝试创建连接,此时该线程进入等待(notEmpty.await()),只有连接对象创建完成或池中回收了连接对象(notEmpty.signal()),该线程才会继续执行。
前面已经讲到,创建连接是采用异步方式,进入到
DruidDataSource.CreateConnectionThread.run()。当不需要创建连接时,该线程进入empty.await()状态,此时需要用户线程调用empty.signal()来唤醒。
进入
DruidDataSource.getConnectionDirect(long)。该方法会使用到validConnectionChecker来校验连接的有效性。
进入
DruidDataSource.getConnectionDirect(long),这里不会进行检测,只是将连接对象放入activeConnections,具体泄露连接的检测工作是在DestroyConnectionThread线程中进行。
DestroyConnectionThread线程会根据我们设置的
timeBetweenEvictionRunsMillis来进行检验,具体的校验会去运行DestroyTask(DruidDataSource的内部类),这里看下DestroyTask的run方法。
进入
DruidDataSource.removeAbandoned(),当连接对象使用时间超过removeAbandonedTimeoutMillis,则会被丢弃掉
进入
DruidDataSource.getConnection。
进入到
FilterChainImpl.dataSource_connect。
这里以
StatFilter.dataSource_getConnection为例。
以上,druid的源码基本已经分析完,其他部分内容有空再做补充。