天天看点

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

之前写了一篇简单介绍过连接池相关的文章,由于过两周公司需要整理一个连接池的文档,所以再补了一篇,本文是基于之前写的一个简单的demo来进行说明,下文有连接

京思祺:DruidDataSoure介绍​zhuanlan.zhihu.com

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

jsq211/druid-demo​github.com

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

在网上的评测中HikariCP连接池相对Druid更快,但是看Druid相关开发人员介绍而言对自身产品性能有足够自信:

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

选用druid的主要目的是用来监控,Druid内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为,本文会稍微做一点点介绍扩展

这篇主要是针对前一篇配置相关通过源码和流程相关做一下说明,包括为什么要这么设置,会影响到哪些地方,以及怎么自定义增加相关的拦截器进行监听

本文主要涉及到以下几个流程:初始化init、开始连接connection、回收close、监听。其中初始化是在第一个连接时产生的,监听也是在初始化时完成

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

初始化相关参数

druid在进行连接时是一个标准的生产消费模式,在init时建立一个可重入锁后,通过各种condition进行生产与消费的区分

druid会在第一个连接时进行初始化

public 
           

在初始化中主要涉及一下部分配置参数

#初始化连接数
initial-size: 10
#最大连接数
max-active: 20
#最小连接数
min-idle: 10
#尝试连接时间
max-wait: 10000
#重试次数
connection-error-retry-attempts: 0
#尝试连接时间
max-wait: 10000
#异步初始化,当初始化线程较多时开启
async-init: true
           

其中initial-size、max-active、min-idle是在初始化时连接时进行的,初始化init-size数量的数据库连接

随后进行以下三个线程的初始化

//日志线程 用来记录日志相关信息
           

日志相关配置信息如下,开启改为true即可,个人更习惯写一个filter记录一下sql查询时间即可

slf4j:
          enabled: false
          statement-create-after-log-enabled: false
          statement-close-after-log-enabled: false
          result-set-open-after-log-enabled: false
          result-set-close-after-log-enabled: false
           

createAndStartCreatorThread是作为一个建立连接的守护线程存在的,且新建和回收2个守护线程是通过CountDownLatch来保证执行完后才能进行后续的流程,新建线程相关的主要配置参数有

#重试次数
connection-error-retry-attempts: 1
#重试间隔
time-between-connect-error-millis: 6000
#失败时是否立即释放占用的锁资源
fail-fast: false
 #失败后是否退出
break-after-acquire-failure: false
           

这里主要说一下,之前碰到的问题中是不能改break-after-acquire-failure这个参数的,在上一篇文章贴的代码中也做了说明,当为true的时候会直接退出循环,此时相当于废弃了新建连接的守护线程

京思祺:记一次DruidDataSource重复请求排查记录​zhuanlan.zhihu.com

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

createAndStartCreatorThread是用来进行回收连接的守护线程,相关参数如下

#检测连接间隔
time-between-eviction-runs-millis: 6000
#追踪连接池泄露回收日志
log-abandoned: false
#回收时间
remove-abandoned-timeout: 6000
#移除连接
remove-abandoned: true
#移除超时时间
remove-abandoned-timeout-millis: 7200000
           

通过timeBetweenEvictionRunsMillis这个参数来进行检测,每隔设定的时间来执行一次定时任务DestroyTask,这个定时任务主要包括了2个内容,一个是收缩现有的连接池shrink,还一个是根据remove-abandoned执行销毁连接

以上就是初始化主要涉及的参数及说明,流程图如下

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

连接 getConnection

如下所示,druid在获取连接时,是在初始化和责任链调用完成以后才进行的

public 
           

涉及到的常用相关配置参数有

#尝试连接时间
max-wait: 10000
#启用非公平锁 在开启maxWait时建议启用
use-unfair-lock: true
#最大等待连接线程数 默认不开启
max-wait-thread-count: 10
#校验sql 在开启校验检测的时候必填
 validation-query: SELECT 1
#查询校验
test-on-borrow: false
#运行
test-while-idle: true
#检测连接间隔
time-between-eviction-runs-millis: 6000
           

在进行连接时,maxWait是最大连接尝试时间,在进行连接时,会通过pollLast从线城市数组connections中取最新的连接放入active中,并进行相应的计数调整,获取失败时,释放锁,启动重试机制

在开启maxWait后可能出现多个连接都在等待的情况,通过max-wait-thread-count进行限制,在出现大量等待的时候直接进行异常抛出即可

在建立连接完成后,进行testOnBorrow和testWhileIdle,testOnBorrow会在每次调用连接时都进行检测,而testWhileIdle会根据time-between-eviction-runs-millis进行定时检测

最后如果开启了remove-abandoned,在建立连接时,连接会被放入activeConnections数组中进行管理,remove-abandoned-timeout-millis是抛弃连接的最大时间,超过该时间时自动废弃,相关流程如下

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

废弃连接close

主要涉及到2个部分吧,在之前init时说到创建了一个销毁线程用来回收连接,里面有一个收缩线程池的方法shirk,代码如下,逻辑其实就是根据之前涉及的检测参数,对数组中的连接进行从头到尾的轮询判断,将不符合要求的连接关闭

public 
           

第二个就是连接池的recycle方法,代码如下,主要包含了几个内容

  1. 是否removeAbandon
  2. 根据traceEnable判断是否进入删除

    在上述1、2中主要是根据removeAbandon和destoryTask2个地方的逻辑,如果已经进行了相关逻辑的操作,则进行回滚

  3. 超出phyMaxUseCount(物理最大连接数?渣翻译,不建议配置,已有maxActive)后进行删除
    1. discardConnection
    2. 调整连接池数量
  4. testOnReturn
  5. 物理最大连接时间phyTimeoutMillis(物理最大连接时间,超出该时间强制删除,不建议)
  6. 不进行删除时进行putLast操作
    1. 时间重置
    2. 放入connections最后
/**
           

相关的流程图如下(shirk和recycle并不是同一个流程!!只是方便说明这样写了)

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

自定义监控Filter

前面几个说完连接池的主要实现基本上就介绍完了,然后是druid的监控功能,号称最强大的连接池监控~这里主要是根据自己的测试进行一些j简单的介绍,还在发掘中~

首先是druid自带的3个监控wall,stat,slf4j,配置信息如下

#
           

这些是常用的参数,当然日志打印的我没有启用,我是继承了FilterEventAdapter这个类重新写了一个简单的日志,如下

@Component
public class TestFilter extends FilterEventAdapter {
    private static final Logger logger = LoggerFactory.getLogger(TestFilter.class);
    private static long timeMessage;
    @Override
    public void init(DataSourceProxy dataSource) {
        logger.info("初始化Filter");
        super.init(dataSource);
    }

    @Override
    protected void statementExecuteBefore(StatementProxy statement, String sql) {
        logger.info("自定义拦截,在执行操作前执行该方法,如打印执行sql:"+sql);
        timeMessage = System.currentTimeMillis();
        super.statementExecuteBefore(statement, sql);
    }

    @Override
    protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {
        super.statementExecuteAfter(statement, sql, result);
        logger.info("自定义拦截器,在执行操作后执行该方法,如打印执行sql:  "+sql);
        if (System.currentTimeMillis() - timeMessage>0){
            logger.error("Error Sql : " + sql);
        }
    }
}
           

配置相关

@Configuration
           

这些代码在官方的tips里面都有~

对于官方提供的拦截器,都是继承了FilterAdapter这个类的,可以看到

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

我们之前配置中引入的类都在这里,相对应的,如果要进行具体拦截信息的查看只需要进入相应的拦截器查找即可,或者我们可以手动写一个监听继承其中某个类,然后覆盖某个方法再加入

以wallFilter为例,简单看一个方法connection_prepareStatement,可以看到,wall在生成事件前对sql进行了一轮检验,从而达到注入防止sql注入等相关信息,具体的配置参数可以在对应的config查看,比如wall就在wallConfig里查看

这部分filter类似于对jdbc做了一层封装,同时又对外提供了一层良好的接口方便对外进行魔改,对应的代码在demo中都有介绍,可以参考

public 
           

尾声

个人感觉在看这个项目时最大的体会就是对连接池建立的梳理,以及对生产消费的设计模式进行了一次巩固,以本人目前的理解看来,最核心的就是理解create线程destory线程和close之间的竞争关系,也是第一次看到用juc包写的项目。。。之前都是在题目里看到的,菜鸡太感动了

druiddatasource_DruidDataSource相关配置及自定义Filter介绍

继续阅读