Druid介绍

Druid介绍

   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的执行性能,这对于线上分析数据库访问性能有帮助。

Druid配置

<dependency>  
  <groupId>com.alibaba</groupId>  
  <artifactId>druid</artifactId>  
  <version>1.0.20</version>  
</dependency>

   数据源的配置:

 <!-- 引入配置文件 -->    
 <bean id="mybatisPropertyConfigurer"    
 class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">    
 <property name="order" value="1" />    
 <property name="ignoreUnresolvablePlaceholders" value="true" />  
 <property name="locations">  
 <list>   
 <value>classpath:druid.properties</value>  
 </list>  
 </property>    
 </bean>    
      
 <!-- druid连接池配置 -->  
 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">    
 <property name="url" value="${url}" />    
 <property name="username" value="${username}" />    
 <property name="password" value="${password}" />    
 <property name="filters" value="${filters}" />    
 <property name="maxActive" value="${maxActive}" />     
 <property name="initialSize" value="${initialSize}" />    
 <property name="maxWait" value="${maxWait}" />    
 <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />  
 <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />  
 <property name="validationQuery" value="${validationQuery}" />    
 <property name="testWhileIdle" value="${testWhileIdle}" />    
 <property name="testOnBorrow" value="${testOnBorrow}" />    
 <property name="testOnReturn" value="${testOnReturn}" />    
 <property name="poolPreparedStatements" value="${poolPreparedStatements}" />    
 <property name="maxPoolPreparedStatementPerConnectionSize" value="${maxPoolPreparedStatementPerConnectionSize}" />  
</bean>

 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的内置监控功能,需要配置数据源时加上<property name="filters" value="stat" />,上面已经有了。


   还需要在web.xml中加上:

 <!-- 启用Web监控统计功能需要在Web应用的web.xml中加入这个Servlet声明 -->  
 <servlet>  
 <servlet-name>DruidStatView</servlet-name>  
 <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>  
 <init-param>    
 <!-- 是否允许清空统计数据,不写时默认true -->  
 <param-name>resetEnable</param-name>    
 <param-value>true</param-value>    
 </init-param>    
 <init-param>    
 <!-- 用户名,用户名和密码可以不写,不写时不需要输入,直接登录 -->  
 <param-name>loginUsername</param-name>    
 <param-value>druid</param-value>    
 </init-param>    
 <init-param>    
 <!-- 密码 -->  
 <param-name>loginPassword</param-name>    
 <param-value>123456</param-value>    
 </init-param>    
 </servlet>  
 <servlet-mapping>  
 <servlet-name>DruidStatView</servlet-name>  
 <url-pattern>/druid/*</url-pattern>  
 </servlet-mapping>

启动项目后在浏览器输入:

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这个类:

DruidDataSource的UML图

图中我只列出了几个重要的属性,这几个属性没有理解好,后面的源码很难看得进去。

概括下初始化的过程

DruidDataSource的初始化时机是可选的,当我们设置init=true时,在createDataSource时就会调用DataSource.init()方法进行初始化,否则,只会在getConnection时再进行初始化。数据源初始化主要逻辑在DataSource.init()这个方法,可以概括为以下步骤:

  1. 加锁
  2. 初始化initStackTrace、id、xxIdSeed、dbTyp、driver、dataSourceStat、connections、evictConnections、keepAliveConnections等属性
  3. 初始化过滤器
  4. 校验maxActive、minIdle、initialSize、timeBetweenLogStatsMillis、useGlobalDataSourceStat、maxEvictableIdleTimeMillis、minEvictableIdleTimeMillis、validationQuery等配置是否合法
  5. 初始化ExceptionSorter、ValidConnectionChecker、JdbcDataSourceStat
  6. 创建initialSize数量的连接
  7. 创建logStatsThread、createConnectionThread和destroyConnectionThread
  8. 等待createConnectionThread和destroyConnectionThread线程run后再继续执行
  9. 注册MBean,用于支持JMX
  10. 如果设置了keepAlive,通知createConnectionThread创建连接对象
  11. 解锁

这个方法差不多200行,考虑篇幅,我删减了部分内容。

加锁和解锁

druid数据源初始化采用的是ReentrantLock,如下:

注意,以下步骤均在这个锁的范围内。

初始化属性

这部分内容主要是初始化一些属性,需要注意的一点就是,这里使用了AtomicLongFieldUpdater来进行原子更新,保证写的安全和读的高效,当然,还是cocurrent包的工具。

初始化过滤器

看到下面的代码会发现,我们还可以通过SPI机制来配置过滤器。

使用SPI配置过滤器时需要注意,对应的类需要加上@AutoLoad注解,另外还需要配置load.spifilter.skip=false,SPI相关内容可参考我的另一篇博客:使用SPI解耦你的实现类。

在这个方法里,主要就是初始化过滤器的一些属性而已。过滤器的部分,本文不会涉及到太多。

校验配置

这里只是简单的校验,不涉及太多复杂的逻辑。

初始化ExceptionSorter、ValidConnectionChecker、JdbcDataSourceStat

这里重点关注ExceptionSorter和ValidConnectionChecker这两个类,这里会根据数据库类型进行选择。其中,ValidConnectionChecker用于对连接进行检测。

创建initialSize数量的连接

这里有两种方式创建连接,一种是异步,一种是同步。但是,根据我们的使用例子,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

接下来是注册MBean,会去注册
DruidDataSourceStatManager和DruidDataSource,启动我们的程度,通过jconsole就可以看到这两个MBean。JMX相关内容这里就不多扩展了,感兴趣的话可参考我的另一篇博客: 如何使用JMX来管理程序?

// 注册MBean,用于支持JMX
        registerMbean();

通知createConnectionThread创建连接对象

前面已经讲过,当我们调用empty.signal(),会去唤醒处于empty.await()状态的CreateConnectionThread。CreateConnectionThread这个线只有在需要创建连接时才运行,否则会一直等待,后面会讲到。

连接对象的获取

了解下DruidPooledConnection这个类

用户调用
DruidDataSource.getConnection,拿到的对象是DruidPooledConnection,里面封装了DruidConnectionHolder,而这个对象包含了原生的连接对象和我们一开始创建的数据源对象。

DruidPooledConnection的UML图

概括下获取连接的过程

连接对象的获取过程可以概括为以下步骤:

  1. 初始化数据源(如果还没初始化);
  2. 获得连接对象,如果无可用连接,向createConnectionThread发送signal创建新连接,此时会进入等待;
  3. 如果设置了testOnBorrow,进行testOnBorrow检测,否则,如果设置了testWhileIdle,进行testWhileIdle检测;
  4. 如果设置了removeAbandoned,则会将连接对象放入activeConnections;
  5. 设置defaultAutoCommit,并返回;
  6. 执行filterChain。

初始化数据源的前面已经讲过了,这里就直接从第二步开始。

获取连接对象

进入
DruidDataSource.getConnectionInternal方法。除了获取连接对象,其他的大部分是校验和计数的内容。

下面再看下DruidDataSource.takeLast()方法(即没有配置maxWait时调用的方法)。该方法中,当没有空闲连接对象时,会尝试创建连接,此时该线程进入等待(notEmpty.await()),只有连接对象创建完成或池中回收了连接对象(notEmpty.signal()),该线程才会继续执行。

创建连接对象

前面已经讲到,创建连接是采用异步方式,进入到
DruidDataSource.CreateConnectionThread.run()。当不需要创建连接时,该线程进入empty.await()状态,此时需要用户线程调用empty.signal()来唤醒。

testOnBorrow或testWhileIdle

进入
DruidDataSource.getConnectionDirect(long)。该方法会使用到validConnectionChecker来校验连接的有效性。

removeAbandoned

进入
DruidDataSource.getConnectionDirect(long),这里不会进行检测,只是将连接对象放入activeConnections,具体泄露连接的检测工作是在DestroyConnectionThread线程中进行。

DestroyConnectionThread线程会根据我们设置的
timeBetweenEvictionRunsMillis来进行检验,具体的校验会去运行DestroyTask(DruidDataSource的内部类),这里看下DestroyTask的run方法。

进入
DruidDataSource.removeAbandoned(),当连接对象使用时间超过removeAbandonedTimeoutMillis,则会被丢弃掉。

执行filterChain

进入
DruidDataSource.getConnection。

进入到
FilterChainImpl.dataSource_connect。

这里以
StatFilter.dataSource_getConnection为例。

以上,druid的源码基本已经分析完,其他部分内容有空再做补充。

数据源的初始化

了解下DruidDataSource这个类

这里先来介绍下DruidDataSource这个类:

DruidDataSource的UML图

图中我只列出了几个重要的属性,这几个属性没有理解好,后面的源码很难看得进去。

概括下初始化的过程

DruidDataSource的初始化时机是可选的,当我们设置init=true时,在createDataSource时就会调用DataSource.init()方法进行初始化,否则,只会在getConnection时再进行初始化。数据源初始化主要逻辑在DataSource.init()这个方法,可以概括为以下步骤:

  1. 加锁
  2. 初始化initStackTrace、id、xxIdSeed、dbTyp、driver、dataSourceStat、connections、evictConnections、keepAliveConnections等属性
  3. 初始化过滤器
  4. 校验maxActive、minIdle、initialSize、timeBetweenLogStatsMillis、useGlobalDataSourceStat、maxEvictableIdleTimeMillis、minEvictableIdleTimeMillis、validationQuery等配置是否合法
  5. 初始化ExceptionSorter、ValidConnectionChecker、JdbcDataSourceStat
  6. 创建initialSize数量的连接
  7. 创建logStatsThread、createConnectionThread和destroyConnectionThread
  8. 等待createConnectionThread和destroyConnectionThread线程run后再继续执行
  9. 注册MBean,用于支持JMX
  10. 如果设置了keepAlive,通知createConnectionThread创建连接对象
  11. 解锁

这个方法差不多200行,考虑篇幅,我删减了部分内容。

加锁和解锁

druid数据源初始化采用的是ReentrantLock,如下:

注意,以下步骤均在这个锁的范围内。

初始化属性

这部分内容主要是初始化一些属性,需要注意的一点就是,这里使用了AtomicLongFieldUpdater来进行原子更新,保证写的安全和读的高效,当然,还是cocurrent包的工具。

初始化过滤器

看到下面的代码会发现,我们还可以通过SPI机制来配置过滤器。

使用SPI配置过滤器时需要注意,对应的类需要加上@AutoLoad注解,另外还需要配置load.spifilter.skip=false,SPI相关内容可参考我的另一篇博客:使用SPI解耦你的实现类。

在这个方法里,主要就是初始化过滤器的一些属性而已。过滤器的部分,本文不会涉及到太多。

校验配置

这里只是简单的校验,不涉及太多复杂的逻辑。

初始化ExceptionSorter、ValidConnectionChecker、JdbcDataSourceStat

这里重点关注ExceptionSorter和ValidConnectionChecker这两个类,这里会根据数据库类型进行选择。其中,ValidConnectionChecker用于对连接进行检测。

创建initialSize数量的连接

这里有两种方式创建连接,一种是异步,一种是同步。但是,根据我们的使用例子,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

接下来是注册MBean,会去注册
DruidDataSourceStatManager和DruidDataSource,启动我们的程度,通过jconsole就可以看到这两个MBean。JMX相关内容这里就不多扩展了,感兴趣的话可参考我的另一篇博客: 如何使用JMX来管理程序?

// 注册MBean,用于支持JMX
        registerMbean();

通知createConnectionThread创建连接对象

前面已经讲过,当我们调用empty.signal(),会去唤醒处于empty.await()状态的CreateConnectionThread。CreateConnectionThread这个线只有在需要创建连接时才运行,否则会一直等待,后面会讲到。

连接对象的获取

了解下DruidPooledConnection这个类

用户调用
DruidDataSource.getConnection,拿到的对象是DruidPooledConnection,里面封装了DruidConnectionHolder,而这个对象包含了原生的连接对象和我们一开始创建的数据源对象。

DruidPooledConnection的UML图

概括下获取连接的过程

连接对象的获取过程可以概括为以下步骤:

  1. 初始化数据源(如果还没初始化);
  2. 获得连接对象,如果无可用连接,向createConnectionThread发送signal创建新连接,此时会进入等待;
  3. 如果设置了testOnBorrow,进行testOnBorrow检测,否则,如果设置了testWhileIdle,进行testWhileIdle检测;
  4. 如果设置了removeAbandoned,则会将连接对象放入activeConnections;
  5. 设置defaultAutoCommit,并返回;
  6. 执行filterChain。

初始化数据源的前面已经讲过了,这里就直接从第二步开始。

获取连接对象

进入
DruidDataSource.getConnectionInternal方法。除了获取连接对象,其他的大部分是校验和计数的内容。

下面再看下DruidDataSource.takeLast()方法(即没有配置maxWait时调用的方法)。该方法中,当没有空闲连接对象时,会尝试创建连接,此时该线程进入等待(notEmpty.await()),只有连接对象创建完成或池中回收了连接对象(notEmpty.signal()),该线程才会继续执行。

创建连接对象

前面已经讲到,创建连接是采用异步方式,进入到
DruidDataSource.CreateConnectionThread.run()。当不需要创建连接时,该线程进入empty.await()状态,此时需要用户线程调用empty.signal()来唤醒。

testOnBorrow或testWhileIdle

进入
DruidDataSource.getConnectionDirect(long)。该方法会使用到validConnectionChecker来校验连接的有效性。

removeAbandoned

进入
DruidDataSource.getConnectionDirect(long),这里不会进行检测,只是将连接对象放入activeConnections,具体泄露连接的检测工作是在DestroyConnectionThread线程中进行。

DestroyConnectionThread线程会根据我们设置的
timeBetweenEvictionRunsMillis来进行检验,具体的校验会去运行DestroyTask(DruidDataSource的内部类),这里看下DestroyTask的run方法。

进入
DruidDataSource.removeAbandoned(),当连接对象使用时间超过removeAbandonedTimeoutMillis,则会被丢弃掉

执行filterChain

进入
DruidDataSource.getConnection。

进入到
FilterChainImpl.dataSource_connect。

这里以
StatFilter.dataSource_getConnection为例。

以上,druid的源码基本已经分析完,其他部分内容有空再做补充。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Druid 是一个开源的高性能实时分析数据库,它提供了丰富的配置选项来满足不同场景的需求。Druid 的配置文件包括了多个部分,下面将详细介绍并给出示例。 1. 数据源配置:数据源配置是 Druid 中最重要的部分之一,其中包括了数据输入输出格式、数据源名称、数据连接信息等。例如: ```json { "dataSources" : { "exampleDataSource" : { "type" : "jdbc", "driver" : "com.mysql.jdbc.Driver", "url" : "jdbc:mysql://localhost:3306/mydb", "user" : "username", "password" : "password" } } } ``` 2. 查询配置:查询配置用于指定查询的相关参数,包括了查询间隔、查询超时时间、查询缓存策略等。例如: ```json { "queryConfig" : { "interval" : "PT1H", "timeout" : "PT30S", "cacheEnable" : true, "cacheSize" : 10000 } } ``` 3. 集群配置:集群配置用于指定 Druid 的集群信息,包括了节点数量、节点角色、集群管理端口等。例如: ```json { "cluster" : { "nodes" : ["node1", "node2", "node3"], "role" : "coordinator", "port" : 8081 } } ``` 4. 监控配置:监控配置用于指定 Druid 监控的相关信息,包括了监控端口、监控地址、监控数据存储路径等。例如: ```json { "monitoring" : { "port" : 8083, "address" : "localhost", "storagePath" : "/var/druid/monitoring" } } ``` 以上是 Druid 配置文件的一些常见部分和示例,通过合理的配置,可以使 Druid 在不同场景下发挥最佳性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值