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配置

  
  com.alibaba  
  druid  
  1.0.20  

   数据源的配置:

     
     
     
   
   
    
 classpath:druid.properties  
   
     
     
      
   
     
     
     
     
     
      
     
     
   
   
     
     
     
     
     
   

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

Druid介绍_第1张图片

DruidDataSource的UML图

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

Druid介绍_第2张图片

概括下初始化的过程

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,如下:

Druid介绍_第3张图片

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

初始化属性

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

Druid介绍_第4张图片

初始化过滤器

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

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

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

Druid介绍_第5张图片

校验配置

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

Druid介绍_第6张图片

初始化ExceptionSorter、ValidConnectionChecker、JdbcDataSourceStat

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

Druid介绍_第7张图片

创建initialSize数量的连接

这里有两种方式创建连接,一种是异步,一种是同步。但是,根据我们的使用例子,createScheduler为null,所以采用的是同步的方式。

注意,后面的所有代码也是基于createScheduler为null来分析的。

Druid介绍_第8张图片

创建logStatsThread、createConnectionThread和destroyConnectionThread

这里会启动三个线程。

Druid介绍_第9张图片

等待

这里使用了CountDownLatch,保证当createConnectionThread和destroyConnectionThread开始run时再继续执行。

private final CountDownLatch initedLatch = new CountDownLatch(2);
        // 线程进入等待,等待CreatorThread和DestroyThread执行
        initedLatch.await();

我们进入到
DruidDataSource.CreateConnectionThread.run(),可以看到,一执行run方法就会调用countDown。destroyConnectionThread也是一样,这里就不放进来了。

Druid介绍_第10张图片

注册MBean

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

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

通知createConnectionThread创建连接对象

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

Druid介绍_第11张图片

连接对象的获取

了解下DruidPooledConnection这个类

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

Druid介绍_第12张图片

DruidPooledConnection的UML图

概括下获取连接的过程

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

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

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

获取连接对象

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

Druid介绍_第13张图片

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

Druid介绍_第14张图片

创建连接对象

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

Druid介绍_第15张图片

testOnBorrow或testWhileIdle

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

Druid介绍_第16张图片

removeAbandoned

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

Druid介绍_第17张图片

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

Druid介绍_第18张图片

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

Druid介绍_第19张图片

执行filterChain

进入
DruidDataSource.getConnection。

Druid介绍_第20张图片

进入到
FilterChainImpl.dataSource_connect。

Druid介绍_第21张图片

这里以
StatFilter.dataSource_getConnection为例。

Druid介绍_第22张图片

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

数据源的初始化

了解下DruidDataSource这个类

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

Druid介绍_第23张图片

DruidDataSource的UML图

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

Druid介绍_第24张图片

概括下初始化的过程

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,如下:

Druid介绍_第25张图片

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

初始化属性

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

Druid介绍_第26张图片

初始化过滤器

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

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

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

Druid介绍_第27张图片

校验配置

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

Druid介绍_第28张图片

初始化ExceptionSorter、ValidConnectionChecker、JdbcDataSourceStat

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

Druid介绍_第29张图片

创建initialSize数量的连接

这里有两种方式创建连接,一种是异步,一种是同步。但是,根据我们的使用例子,createScheduler为null,所以采用的是同步的方式。

注意,后面的所有代码也是基于createScheduler为null来分析的。

Druid介绍_第30张图片

创建logStatsThread、createConnectionThread和destroyConnectionThread

这里会启动三个线程。

Druid介绍_第31张图片

等待

这里使用了CountDownLatch,保证当createConnectionThread和destroyConnectionThread开始run时再继续执行。

private final CountDownLatch initedLatch = new CountDownLatch(2);
        // 线程进入等待,等待CreatorThread和DestroyThread执行
        initedLatch.await();

我们进入到
DruidDataSource.CreateConnectionThread.run(),可以看到,一执行run方法就会调用countDown。destroyConnectionThread也是一样,这里就不放进来了。

Druid介绍_第32张图片

注册MBean

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

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

通知createConnectionThread创建连接对象

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

Druid介绍_第33张图片

连接对象的获取

了解下DruidPooledConnection这个类

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

Druid介绍_第34张图片

DruidPooledConnection的UML图

概括下获取连接的过程

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

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

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

获取连接对象

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

Druid介绍_第35张图片

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

Druid介绍_第36张图片

创建连接对象

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

Druid介绍_第37张图片

testOnBorrow或testWhileIdle

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

Druid介绍_第38张图片

removeAbandoned

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

Druid介绍_第39张图片

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

Druid介绍_第40张图片

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

Druid介绍_第41张图片

执行filterChain

进入
DruidDataSource.getConnection。

Druid介绍_第42张图片

进入到
FilterChainImpl.dataSource_connect。

Druid介绍_第43张图片

这里以
StatFilter.dataSource_getConnection为例。

Druid介绍_第44张图片

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

你可能感兴趣的:(android,adb)