Druid详解

Druid介绍

Druid首先是一个数据库连接池。Druid是目前最好的数据库连接池,在功能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Druid是阿里巴巴开发的号称为监控而生的数据库连接池!

同时Druid不仅仅是一个数据库连接池,它包括四个部分:
  • Druid是一个JDBC组件。
  • 基于Filter-Chain模式的插件体系。
  • DruidDataSource 高效可管理的数据库连接池。
  • SQLParser (sql解析器)
Druid的功能
  • Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
  • 可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
  • 数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。
  • SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。
  • 扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter机制,很方便编写JDBC层的扩展插件。
优势
  • 充当数据库连接池。

  • 可以监控数据库访问性能

  • 获得SQL执行日志

    配置

    配置默认值说明
    name配置这个属性的意义在于,如果存在多个数据源,监控的时候
    可以通过名字来区分开来。如果没有配置,将会生成一个名字,
    格式是:“DataSource-” + System.identityHashCode(this)
    jdbcUrl连接数据库的url,不同数据库不一样。例如:
    mysql : jdbc:mysql://10.20.153.104:3306/druid2
    oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
    username连接数据库的用户名
    password连接数据库的密码。如果你不希望密码直接写在配置文件中
    可以使用ConfigFilter。详细看这里:
    https://github.com/alibaba/druid/wiki/使用ConfigFilter
    driverClassName根据url自动识别这一项可配可不配,如果不配置druid会根据url自动识别dbType,
    然后选择相应的driverClassName
    initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,
    或者第一次getConnection时
    maxActive8最大连接池数量
    minIdle最小连接池数量
    maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,
    缺省启用公平锁,并发效率会有所下降,
    如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
    poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。
    PSCache对支持游标的数据库性能提升巨大,比如说oracle。
    在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。
    5.5及以上版本有PSCache,建议开启。
    maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,
    poolPreparedStatements自动触发修改为true。
    在Druid中,不会存在Oracle下PSCache占用内存过多的问题,
    可以把这个数值配置大一些,比如说100
    validationQuery用来检测连接是否有效的sql,要求是一个查询语句。
    如果validationQuery为null,testOnBorrow、testOnReturn、
    testWhileIdle都不会其作用。在mysql中通常为select ‘x’,在oracle中通常为
    select 1 from dual
    testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,
    做了这个配置会降低性能。
    testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,
    做了这个配置会降低性能
    testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。
    申请连接的时候检测,如果空闲时间大于
    timeBetweenEvictionRunsMillis,
    执行validationQuery检测连接是否有效。
    timeBetweenEvictionRunsMillis有两个含义:
    1) Destroy线程会检测连接的间隔时间
    2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
    minEvictableIdleTimeMillisDestory线程中如果检测到当前连接的最后活跃时间和当前时间的差值大于
    minEvictableIdleTimeMillis,则关闭当前连接。
    connectionInitSqls物理连接初始化的时候执行的sql
    exceptionSorter根据dbType自动识别当数据库抛出一些不可恢复的异常时,抛弃连接
    filters属性类型是字符串,通过别名的方式配置扩展插件,
    常用的插件有:
    监控统计用的filter:stat
    日志用的filter:log4j
    防御sql注入的filter:wall
    proxyFilters类型是List<com.alibaba.druid.filter.Filter>,
    如果同时配置了filters和proxyFilters,
    是组合关系,并非替换关系

常见问题

参考:

druid的wiki

数据库连接池初始化

图解:

image

获取数据库连接

图解:

image

如果当前数据库连接池有可用连接,则直接获取,否者如果设置createScheduler则开启一个新线程去创建物理连接,否者发送发送signal信号唤醒连接创建线程创建数据库物理连接,这种情况有分pollLast和takeLast,区别在于前者设置max
wait超时时间,如果在时间内还没获取链接则返回null,后者则一直等待连接创建。

回收数据库连接

图解:

image

在调用conn.close时候会回收当前连接到线程池

创建数据库连接线程

图解:

image

此图createScheduler=null的情况,为null从上面知道在初始化时候会新建
一个数据库连接创建线程。

连接池同步策略

实际上是个生产者消费者模式,生产者是连接创建线程和连接回收线程,消费者是获取连接的线程。

首先列下同步需要的条件变量和锁:

//独占锁(用来控制同是只有一个线程访问线程池),根据lockFair分为公平和非公平锁
 lock     = new ReentrantLock(lockFair);
 
 //用来对消费和生成线程做同步
 notEmpty = lock.newCondition();
 empty    = lock.newCondition();
 
 //共享资源,线程池是一个数组
 connections = new DruidConnectionHolder[maxActive];

消费者(获取数据库连接线程):

DruidConnectionHolder pollLast(){
    
    DruidConnectionHolder holder;

    try {
            //加独占锁
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            connectErrorCount.incrementAndGet();
            throw new SQLException("interrupt", e);
    }

    try {

        //通知连接创建线程,创建数据库连接
        empty.signal();

        //等待连接创建线程或者连接回收线程发送信号
        estimate = notEmpty.awaitNanos(estimate);

        //获取链接,并返回
        decrementPoolingCount();
        holder = connections[poolingCount];
        connections[poolingCount] = null;
   

    }finally{
        //释放独占锁
        lock.unlock();

    }


    holder.incrementUseCount();
    DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);

    return poolalbeConnection;
}

生产者(连接创建线程,代码版本,druid 1.0.12,目前正在使用):

public void run() {
            for (;;) {
                // addLast
                try {
                    lock.lockInterruptibly();
                } catch (InterruptedException e2) {
                    break;
                }

                try {
                    // 必须存在线程等待,才创建连接
                    if (poolingCount >= notEmptyWaitThreadCount) {
                        createTaskCount--;
                        return;
                    }

                    // 防止创建超过maxActive数量的连接
                    if (activeCount + poolingCount >= maxActive) {
                        createTaskCount--;
                        return;
                    }
                } finally {
                    lock.unlock();
                }

                Connection connection = null;

                try {
                    connection = createPhysicalConnection();
                } catch (SQLException e) {
                    LOG.error("create connection error, url: " + jdbcUrl, e);

                    errorCount++;

                    if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
                        if (breakAfterAcquireFailure) {
                            createTaskCount--;
                            return;
                        }

                        createScheduler.schedule(this, timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS);
                        return;
                    }
                } catch (RuntimeException e) {
                    LOG.error("create connection error", e);
                    continue;
                } catch (Error e) {
                    LOG.error("create connection error", e);
                    break;
                }

                if (connection == null) {
                    continue;
                }

                put(connection);
                break;
            }
        }

生产者(数据库连接回收线程):

protected void recycle(DruidPooledConnection pooledConnection){
    lock.lockInterruptibly();
    try {
        activeCount--;
        closeCount++;

        connections[poolingCount] = e;
        incrementPoolingCount();

        if (poolingCount > poolingPeak) {
            poolingPeak = poolingCount;
            poolingPeakTime = lastActiveTimeMillis;
        }

        //激活消费线程
        notEmpty.signal();
        notEmptySignalCount++;      
        recycleCount++;
    } finally {
        lock.unlock();
    }
}

源码摘自:
com.alibaba.druid.pool.DruidDataSource

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值