Druid 连接池介绍

DruidDataSource共享锁

public abstract class DruidAbstractDataSource extends WrapperAdapter implements DruidAbstractDataSourceMBean, DataSource, DataSourceProxy, Serializable {
    // 可重入锁 lock
    protected ReentrantLock                            lock;
    // 非空条件变量
    protected Condition                                notEmpty;
    // 空条件变量
    protected Condition                                empty;

    public DruidAbstractDataSource(boolean lockFair){
        lock = new ReentrantLock(lockFair);
        notEmpty = lock.newCondition();
        empty = lock.newCondition();
    }
}

public class DruidDataSource extends DruidAbstractDataSource implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {

    private volatile DruidConnectionHolder[] connections;
    private DruidConnectionHolder[]          evictConnections;
    private DruidConnectionHolder[]          keepAliveConnections;

    private CreateConnectionThread           createConnectionThread;
    private DestroyConnectionThread          destroyConnectionThread;
    private LogStatsThread                   logStatsThread;
}
  • DruidDataSource的三个变量通过构造函数初始化,可以指定采用公平锁或者非公平锁。
  • 生产者和消费者的任何操作都需要获得lock。生产者根据empty条件变量await,当连接池中连接被消耗完后触发empty的通知的时候,阻塞在empty上的生产者开始创建连接。创建完成之后生产者发送notEmpty的sigal信号,触发在notEmpty上的消费者来获取连接进行消费。这是Druid连接池的基本原理。
  • DruidDataSource连接池的缓冲区位于DruidDataSource中的DruidConnectionHolder[]数组中。

DruidDataSource连接池

  • CreateConnectionThread是连接池中的创建连接的线程,当连接池中的连接不足的时候负责创建连接。
  • DestroyConnectionThread是连接池中的销毁连接线程,当连接池中出现空闲连接超过配置的空闲连接数,或者出现一些不健康的连接,那么线程池将会通过DestroyConnectionThread线程将连接回收。

DruidDataSource 核心线程

    public class CreateConnectionThread extends Thread {

        public CreateConnectionThread(String name){
            super(name);
            this.setDaemon(true);
        }

        public void run() {

            initedLatch.countDown();

            long lastDiscardCount = 0;
            int errorCount = 0;
            for (;;) {
                // addLast
                try {
                    lock.lockInterruptibly();
                } catch (InterruptedException e2) {
                    break;
                }

                long discardCount = DruidDataSource.this.discardCount;
                boolean discardChanged = discardCount - lastDiscardCount > 0;
                lastDiscardCount = discardCount;

                try {
                    boolean emptyWait = true;

                    if (createError != null
                            && poolingCount == 0
                            && !discardChanged) {
                        emptyWait = false;
                    }

                    if (emptyWait
                            && asyncInit && createCount < initialSize) {
                        emptyWait = false;
                    }
                    // 判断是否需要等待连接池为空的信号量 empty.wait()
                    if (emptyWait) {
                        // 必须存在线程等待,才创建连接
                        if (poolingCount >= notEmptyWaitThreadCount //
                                && (!(keepAlive && activeCount + poolingCount < minIdle))
                                && !isFailContinuous()
                        ) {
                            empty.await();
                        }

                        // 防止创建超过maxActive数量的连接
                        if (activeCount + poolingCount >= maxActive) {
                            empty.await();
                            continue;
                        }
                    }

                } catch (InterruptedException e) {
                    break;
                } finally {
                    lock.unlock();
                }

                // 负责创建连接
                PhysicalConnectionInfo connection = null;
                try {
                    connection = createPhysicalConnection();
                } catch (Error e) {
                    setFailContinuous(true);
                    break;
                }

                // 添加新建的连接
                boolean result = put(connection);

                errorCount = 0; // reset errorCount
            }
        }
    }
  • CreateConnectionThread负责创建连接放到连接池当中,当连接池的连接数足够的时候通过empty.await()进行阻塞等待,否则创建连接通过 put 放到连接池。
    public class DestroyConnectionThread extends Thread {

        public DestroyConnectionThread(String name){
            super(name);
            this.setDaemon(true);
        }

        public void run() {
            initedLatch.countDown();

            for (;;) {
                // 从前面开始删除
                try {
                    if (closed) {
                        break;
                    }
                    // 等待连接驱除等待时间
                    if (timeBetweenEvictionRunsMillis > 0) {
                        Thread.sleep(timeBetweenEvictionRunsMillis);
                    } else {
                        Thread.sleep(1000); //
                    }

                    if (Thread.interrupted()) {
                        break;
                    }

                    destroyTask.run();
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }

    public class DestroyTask implements Runnable {
        public DestroyTask() {
        }

        @Override
        public void run() {
            // 缩减连接池
            shrink(true, keepAlive);

            if (isRemoveAbandoned()) {
                removeAbandoned();
            }
        }

    }

Druid 监控参数

参数 含义
ActiveCount 0 当前连接池中活跃连接数
ActivePeak 1 连接池中活跃连接数峰值
ActivePeakTime 2020/4/13 16:12 活跃连接池峰值出现的时间
BlobOpenCount 0 Blob打开数
ClobOpenCount 0 Clob打开数
CommitCount 0 提交数
ConnectionHoldTimeHistogram 0,0,0,0,0,0,0,0 连接持有时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s],这个值是一个数组,数值的索引位的含义如上述,第几索引上的数据就代表在这个时间区间内包含的连接数
ErrorCount 0 错误数
ExecuteCount 0 执行数
InitialSize 60 连接池建立时创建的初始化连接数
LogicCloseCount 2 产生的逻辑连接关闭总数
LogicConnectCount 2 产生的逻辑连接建立总数
LogicConnectErrorCount 0 产生的逻辑连接出错总数
LoginTimeout 0 数据库客户端登录超时时间
MaxActive 200 连接池中最大的活跃连接数
MinIdle 120 连接池中最小的活跃连接数
NotEmptyWaitCount 0 获取连接时最多等待多少次
NotEmptyWaitMillis 0 获取连接时最多等待多长时间,毫秒为单位
PSCacheAccessCount 0 PSCache访问总数
PSCacheHitCount 0 PSCache命中次数
PSCacheMissCount 0 PSCache未命中次数
PhysicalCloseCount 0 产生的物理关闭总数
PhysicalConnectCount 60 产生的物理连接建立总数
PhysicalConnectErrorCount 0 产生的物理连接失败总数
PoolingCount 60 当前连接池中的连接数
PoolingPeak 60 连接池中连接数的峰值
PoolingPeakTime 2020/4/13 16:12 连接池数目峰值出现的时间
QueryTimeout 0 查询超时数
RollbackCount 0 回滚数
StartTransactionCount 0 事务开始的个数
TransactionHistogram 0,0,0,0,0,0,0,0 事务运行时间分布,分布区间为[0-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s],这个值是一个数组,数值的索引位的含义如上述,第几索引上的数据就代表在这个时间区间内包含的事务数
TransactionQueryTimeout 0 事务查询超时数
WaitThreadCount 0 当前等待获取连接的线程数

Druid 配置参数

配置 缺省值 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:"DataSource-" + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错。详情-点此处
url 连接数据库的url,不同数据库不一样。
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxPoolPreparedStatementPerConnectionSize -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
validationQueryTimeout 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
keepAlive false
(1.0.28) 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
timeBetweenEvictionRunsMillis 1分钟(1.0.14) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接;testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 30分钟(1.0.14) 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis 连接保持空闲而不被驱逐的最小时间
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,监控统计用的filter:stat;日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,809评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,189评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,290评论 0 359
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,399评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,425评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,116评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,710评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,629评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,155评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,261评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,399评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,068评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,758评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,252评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,381评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,747评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,402评论 2 358

推荐阅读更多精彩内容