`
m635674608
  • 浏览: 5043034 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

阿里连接池Druid源码中的部分配置研究

 
阅读更多

Druid是一个JDBC组件,它包括三部分:

  • DruidDriver 代理Driver,能够提供基于Filter-Chain模式的插件体系。
  • DruidDataSource 高效可管理的数据库连接池。
  • SQLParser

 

常见配置说明

配置
默认值
说明
name
 
配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
jdbcUrl
 
连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username
 
连接数据库的用户名
password
 
连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName
根据url自动识别
这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize
0
初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive
8
连接池中最大连接数量
maxIdle
8
连接池中最大连接数量,已经不再使用,配置了也没效果
minIdle
0
连接池中最小连接数量
maxWait
-1
获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements
false
是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。5.5及以上版本有PSCache,建议开启。
maxOpenPreparedStatements
-1
要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
maxPoolPreparedStatementsPerConnectionSize
10
为每个连接缓存的preparedStatement的最大数量
validationQuery
null
用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用
validationQueryTimeout
-1
执行validationQuery的超时时间
testOnBorrow
false
申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturn
false
归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle
true
建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis
-1L(最新版本1.0.17中已改为60 * 1000L)
有两个含义:
1) Destroy线程检测连接的间隔时间,此时如果未配置,则间隔1000毫秒
2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
minEvictableIdleTimeMillis
1000L * 60L * 30L
空闲连接回收的最小空闲时间
numTestsPerEvictionRun
3
不再使用,一个DruidDataSource只支持一个EvictionRun
connectionInitSqls
 
物理连接初始化的时候执行的sql
exceptionSorter
null,根据dbType自动识别
当数据库抛出一些不可恢复的异常时,抛弃连接
filters
 
属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
监控统计用的filter:stat
日志用的filter:log4j
防御sql注入的filter:wall
proxyFilters
 
类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
removeAbandoned
false
是否强制关闭连接时长大于removeAbandonedTimeoutMillis的连接
removeAbandonedTimeoutMillis
300 * 1000
一个连接从被连接到被关闭之间的最大生命周期
logAbandoned
false
强制关闭连接时是否记录日志
queryTimeout
 
执行查询的超时时间(秒),执行Statement对象时如果超过此时间,则抛出SQLException
transactionQueryTimeout
 
执行一个事务的超时时间(秒),执行Statement对象 时判断是否为事务,如果是且此项未设置,则使用queryTimeout

部分配置项生效过程理解
配置项中指定了各个参数后,在连接池内部是这么使用这些参数的。数据库连接池在初始化的时候会创建initialSize个连接,当有数据库操作时,会从 池中取出一个连接。如果当前池中正在使用的连接数等于maxActive,则会等待一段时间,等待其他操作释放掉某一个连接,如果这个等待时间超过了 maxWait,则会报错;如果当前正在使用的连接数没有达到maxActive,则判断当前是否有空闲连接,如果有则直接使用空闲连接,如果没有则新建 立一个连接。在连接使用完毕后,不是将其物理连接关闭,而是将其放入池中等待其他操作复用。

    public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
        init();

        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(this);
            return filterChain.dataSource_connect(this, maxWaitMillis);
        } else {
            return getConnectionDirect(maxWaitMillis);
        }
    }

    public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
        int notFullTimeoutRetryCnt = 0;
        for (;;) {
            // handle notFullTimeoutRetry
            DruidPooledConnection poolableConnection;
            try {
                poolableConnection = getConnectionInternal(maxWaitMillis);
            } catch (GetConnectionTimeoutException ex) {
                if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
                    notFullTimeoutRetryCnt++;
                    if (LOG.isWarnEnabled()) {
                        LOG.warn("not full timeout retry : " + notFullTimeoutRetryCnt);
                    }
                    continue;
                }
                throw ex;
            }

            if (isTestOnBorrow()) {
                boolean validate = testConnectionInternal(poolableConnection.getConnection());
                if (!validate) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("skip not validate connection.");
                    }

                    Connection realConnection = poolableConnection.getConnection();
                    discardConnection(realConnection);
                    continue;
                }
            } else {
                Connection realConnection = poolableConnection.getConnection();
                if (realConnection.isClosed()) {
                    discardConnection(null); // 传入null,避免重复关闭
                    continue;
                }

                if (isTestWhileIdle()) {
                    final long currentTimeMillis = System.currentTimeMillis();
                    final long lastActiveTimeMillis = poolableConnection.getConnectionHolder().getLastActiveTimeMillis();
                    final long idleMillis = currentTimeMillis - lastActiveTimeMillis;
                    long timeBetweenEvictionRunsMillis = this.getTimeBetweenEvictionRunsMillis();
                    if (timeBetweenEvictionRunsMillis <= 0) {
                        timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
                    }

                    if (idleMillis >= timeBetweenEvictionRunsMillis) {
                        boolean validate = testConnectionInternal(poolableConnection.getConnection());
                        if (!validate) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("skip not validate connection.");
                            }

                            discardConnection(realConnection);
                            continue;
                        }
                    }
                }
            }
            ……

            return poolableConnection;
        }
    }

同时连接池内部有机制判断,如果当前的总的连接数少于minIdle,则会建立新的空闲连接,以保证连接数达到minIdle。以上源码可以看出, 当testWhileIdle参数为true时,在获取连接时将对得到的连接进行检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,则执行validationQuery验证连接是否有效。

    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 {
        @Override
        public void run() {
            shrink(true);

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

    }

    public void shrink(boolean checkTime) {
        final List evictList = new ArrayList();
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            return;
        }

        try {
            final int checkCount = poolingCount - minIdle;
            final long currentTimeMillis = System.currentTimeMillis();
            for (int i = 0; i < checkCount; ++i) {
                DruidConnectionHolder connection = connections[i];

                long phyConnectTimeMillis = connection.getTimeMillis() - currentTimeMillis;//physical connection connected time
                if( phyConnectTimeMillis  > phyTimeoutMillis  ){
                    evictList.add(connection);//if physical connection connected greater than phyTimeoutMillis, close the connection, for mysql 8 hours timeout
                    continue;
                }

                if (checkTime) {
                    long idleMillis = currentTimeMillis - connection.getLastActiveTimeMillis();
                    if (idleMillis >= minEvictableIdleTimeMillis) {
                        evictList.add(connection);
                    } else {
                        break;
                    }
                } else {
                    evictList.add(connection);
                }
            }

            int removeCount = evictList.size();
            if (removeCount > 0) {
                System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
                Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
                poolingCount -= removeCount;
            }
        } finally {
            lock.unlock();
        }

        for (DruidConnectionHolder item : evictList) {
            Connection connection = item.getConnection();
            JdbcUtils.close(connection);
            destroyCount.incrementAndGet();
        }
    }

    public int removeAbandoned() {
        int removeCount = 0;

        long currrentNanos = System.nanoTime();

        List abandonedList = new ArrayList();

        synchronized (activeConnections) {
            Iterator iter = activeConnections.keySet().iterator();

            for (; iter.hasNext();) {
                DruidPooledConnection pooledConnection = iter.next();

                if (pooledConnection.isRunning()) {
                    continue;
                }

                long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);

                if (timeMillis >= removeAbandonedTimeoutMillis) {
                    iter.remove();
                    pooledConnection.setTraceEnable(false);
                    abandonedList.add(pooledConnection);
                }
            }
        }

        if (abandonedList.size() > 0) {
            for (DruidPooledConnection pooledConnection : abandonedList) {
                synchronized (pooledConnection) {
                    if (pooledConnection.isDisable()) {
                        continue;
                    }
                }
                
                JdbcUtils.close(pooledConnection);
                pooledConnection.abandond();
                removeAbandonedCount++;
                removeCount++;

                if (isLogAbandoned()) {
                    StringBuilder buf = new StringBuilder();
                    buf.append("abandon connection, owner thread: ");
                    buf.append(pooledConnection.getOwnerThread().getName());
                    buf.append(", connected at : ");
                    buf.append(pooledConnection.getConnectedTimeMillis());
                    buf.append(", open stackTrace\n");

                    StackTraceElement[] trace = pooledConnection.getConnectStackTrace();
                    for (int i = 0; i < trace.length; i++) {
                        buf.append("\tat ");
                        buf.append(trace[i].toString());
                        buf.append("\n");
                    }

                    buf.append("ownerThread current state is "+pooledConnection.getOwnerThread().getState() + ", current stackTrace\n");
                    trace = pooledConnection.getOwnerThread().getStackTrace();
                    for (int i = 0; i < trace.length; i++) {
                        buf.append("\tat ");
                        buf.append(trace[i].toString());
                        buf.append("\n");
                    }

                    LOG.error(buf.toString());
                }
            }
        }

        return removeCount;
    }

以上源码可以看出每隔timeBetweenEvictionRunsMillis进行一次shrink(连接池大小收缩)检测,如果当前连接池中 的连接数量大于minIdle,则对超出minIdle的较早的连接进行空闲时间检测,如果某个连接在空闲了 minEvictableIdleTimeMillis时间后仍然没有使用,则被物理性的关闭掉。除了定时检测空闲连接以外,Druid还有一个 removeAbandoned机制,如果removeAbandoned为true,则在执行shrink后执行removeAbandoned(), 如果某个连接从被连接到当前时间超过removeAbandonedTimeoutMillis,则无论是否被使用都被强制物理性的关闭掉,同时将该连接 的abandoned设置为true。

    public int getTransactionQueryTimeout() {
        if (transactionQueryTimeout <= 0) {
            return queryTimeout;
        }

        return transactionQueryTimeout;
    }

    public void setTransactionQueryTimeout(int transactionQueryTimeout) {
        this.transactionQueryTimeout = transactionQueryTimeout;
    }

    /**
     * Retrieves the number of seconds the driver will wait for a Statement object to execute. If the limit
     * is exceeded, a SQLException is thrown.
     * 
     * @return the current query timeout limit in seconds; zero means there is no limit
     * @exception SQLException if a database access error occurs or this method is called on a closed
     * Statement
     * @see #setQueryTimeout
     */
    public int getQueryTimeout() {
        return queryTimeout;
    }

    /**
     * Sets the number of seconds the driver will wait for a Statement object to execute to the given
     * number of seconds. If the limit is exceeded, an SQLException is thrown. A JDBC driver must apply
     * this limit to the execute, executeQuery and executeUpdate methods. JDBC
     * driver implementations may also apply this limit to ResultSet methods (consult your driver vendor
     * documentation for details).
     * 
     * @param seconds the new query timeout limit in seconds; zero means there is no limit
     * @exception SQLException if a database access error occurs, this method is called on a closed
     * Statement or the condition seconds >= 0 is not satisfied
     * @see #getQueryTimeout
     */
    public void setQueryTimeout(int seconds) {
        this.queryTimeout = seconds;
    }

    void initStatement(DruidPooledConnection conn, Statement stmt) throws SQLException {
        boolean transaction = !conn.getConnectionHolder().isUnderlyingAutoCommit();

        int queryTimeout = transaction ? getTransactionQueryTimeout() : getQueryTimeout();

        if (queryTimeout > 0) {
            stmt.setQueryTimeout(queryTimeout);
        }
    }

以上源码中可以看出在初始化Statement对象时,会设置其queryTimeout,如果conn开启了事务,则使用 transactionQueryTimeout,没开启就直接用queryTimeout。使用transactionQueryTimeout的时 候,如果该项没有设置,依然直接使用queryTimeout。此参数的作用是执行Statement对象时如果超过此时间,就抛出 SQLException。

有些数据库连接的时候有超时限制(mysql连接在8小时后断开),或者由于网络中断等原因,连接池的连接会出现失效的情况,这时候设置一个 testWhileIdle参数为true,可以保证连接池内部定时检测连接的可用性,不可用的连接会被抛弃或者重建,最大情况的保证从连接池中得到的 Connection对象是可用的。当然,为了保证绝对的可用性,你也可以使用testOnBorrow为true(即在获取Connection对象时 检测其可用性),不过这样会影响性能。

 

http://www.itechzero.com/alibaba-druid-datasource-source-study-part-of-properties.html

分享到:
评论

相关推荐

    阿里巴巴数据库连接池druid及其源码

    **阿里巴巴数据库连接池Druid详解** Druid是阿里巴巴开源的一款高效、强大且可扩展的数据库连接池组件。作为Java世界中的优秀数据源管理工具,它不仅提供了基础的数据库连接池功能,还内置了丰富的监控和扩展特性,...

    java数据库连接池Druid

    Druid是阿里巴巴开源的一款优秀的数据库连接池实现,它不仅提供了基本的连接池功能,还集成了监控、日志、SQL解析等高级特性。本篇文章将深入探讨Druid的核心特性和使用方法。 1. **简介** Druid是阿里巴巴在2010...

    druid数据库连接池详细属性配置

    Druid是阿里巴巴开源的一款高效、强大的数据库连接池组件,它在性能、监控和扩展性方面表现出色。Druid不仅是一个连接池,还包含了SQL解析器、日志监控、SQL执行效率分析等功能,使得数据库的管理变得更加便捷和智能...

    druid连接池

    通过学习和研究这些源代码,开发者不仅可以了解Druid连接池的工作原理,还能学习到如何在项目中高效使用和自定义配置Druid,提升系统的稳定性和性能。同时,对于Java的数据库编程和设计模式也会有更深入的理解。

    Druid源码(druid-1.2.8.tar.gz)

    在Druid源码中,我们可以重点关注以下几个核心模块: 1. 数据源(DataSource):作为数据库连接池的核心,DruidDataSource提供了初始化、连接获取与释放、连接池状态监控等功能。它支持配置各种参数,如最大连接数...

    druid连接池源码和jar包下载

    Druid是阿里巴巴开源的一款高性能、功能强大的数据库连接池组件,它在Java编程领域中被广泛使用,特别是在大型互联网项目中。Druid提供了监控、SQL解析、防御性编程等功能,旨在提高数据库连接管理的效率和系统的...

    druid源码配置文件和jar包.rar

    Druid是一个广泛使用的Java数据库连接池(JDBC Connection Pool)框架,由阿里巴巴开源。它不仅提供了数据库连接池功能,还包含SQL解析、监控统计、日志等特性,旨在提高数据库操作的性能和稳定性。在本压缩包"druid...

    druid1.2.8 下载 数据库连接池

    Druid是阿里巴巴开源的一个高性能、多功能的数据库连接池组件,它在Java开发中广泛应用于各种项目的数据库管理。在1.2.8版本中,Druid提供了一系列优化和改进,旨在提高数据库访问性能,增强监控能力,并简化数据库...

    druid, 为监控而生的数据库连接池!阿里云DRDS(https.zip

    Druid,一个专为监控而设计的数据库连接池,由阿里巴巴开源并广泛应用于各种项目中,尤其是阿里云的分布式关系型数据库服务DRDS。这个压缩包`druid-master`包含了Druid项目的源代码,可供开发者深入学习和定制。 ...

    数据库连接池,druid依赖包

    在Java开发中,Druid是一个广泛使用的数据库连接池实现,由阿里巴巴开源并维护。标题提到的"数据库连接池,druid依赖包"正是针对这个组件。 Druid是一个多功能的数据源库,不仅提供了数据库连接池服务,还集成了SQL...

    Druid数据池源码下载

    通过阅读和分析Druid源码,我们可以学习到如何设计和实现一个高性能的数据库连接池,同时也能了解阿里巴巴在开发过程中的一些最佳实践和设计模式。这对于提升个人技能,特别是Java后端开发能力,有着极大的帮助。...

    druid-1.1.6 源码包

    Druid是阿里巴巴开源的一个数据库连接池组件,它在Java开发中被广泛应用,特别是在大数据量、高并发的系统中,因其高效、稳定和强大的监控能力而受到赞誉。本源码包为`druid-1.1.6`版本,包含了Druid的核心功能及其...

    阿里数据库连接池.zip

    总之,阿里数据库连接池Druid是Java开发中的重要工具,它提供的不仅仅是简单的数据库连接管理,还包括了丰富的监控、安全防护和性能优化功能。结合源码学习和配置文件的使用,开发者可以更好地掌握Druid的精髓,提升...

    基于Java的Druid数据库连接池设计源码解析

    该项目是基于Java开发的Druid数据库连接池设计源码,包含4690个文件,涵盖4070个Java源文件、297个SQL文件、102个TXT文件、94个DAT文件、19个PROPERTIES文件、19个HTML文件、15个XML文件、13个JAR文件、8个PKS文件和...

    连接池源码和视频教程

    - Druid:阿里巴巴开源的数据库连接池,除了基本的连接池功能外,还提供了监控、日志、拦截器等功能,便于对数据库访问进行诊断和优化。 4. 使用步骤: - 引入依赖:在项目中引入对应连接池的jar包或Maven/Gradle...

    druid-1.2.8.jar

    Druid连接池,全称是Druid Data Source,是由阿里巴巴开源的一个高效、强大的数据库连接池组件。在Java开发中,数据库连接池是管理数据库连接的重要工具,它可以提高数据库访问的效率,减少系统资源的浪费,是现代...

    参照阿里druid整理druid-spring-boot-starter的demo

    通过引入这个启动器,我们可以快速地在Spring Boot项目中配置并使用Druid数据库连接池。`druid-spring-boot-starter`自动配置了数据源、监控统计以及相关的初始化设置,使得开发者无需编写大量繁琐的XML配置或Java...

    C3P0与druid连接池

    这个资料包中的`druid-1.0.9`和`C3P0`文件可能包含了这两个连接池的库文件,以及相关的配置示例,对于学习者来说,可以通过阅读源码和实践配置,深入了解它们的内部实现和使用方法。在实际项目中,结合提供的配置...

    基于Java的阿里巴巴数据库事业部监控专属数据库连接池设计源码

    该项目为阿里巴巴数据库事业部推出的针对监控...该连接池由Druid驱动,支持阿里云DRDS和阿里巴巴TDDL,旨在提供高效、稳定的数据库连接管理。2018年,该项目荣获开源中国最受欢迎开源软件评选奖项,欢迎参与投票支持。

    Spring MVC 配置 druid 数据源实例

    而Druid是阿里巴巴开源的一个高性能、多功能的Java数据库连接池,它不仅提供了基本的连接池功能,还具备SQL拦截、统计分析、监控等功能,对于优化数据库访问性能有着显著帮助。 要配置Druid数据源,我们首先需要在...

Global site tag (gtag.js) - Google Analytics