数据库链接池原理详解与自定义链接池实现

实现原理

数据库链接池在初始化时将建立必定数量的数据库链接放到链接池中,这些数据库链接的数量是由最小数据库链接数制约。不管这些数据库链接是否被使用,链接池都将一直保证至少拥有这么多的链接数量。链接池的最大数据库链接数量限定了这个链接池能占有的最大链接数,当应用程序向链接池请求的链接数超过最大链接数量时,这些请求将被加入到等待队列中。 
链接池基本的思想是在系统初始化的时候,将数据库链接做为对象存储在内存中,当用户须要访问数据库时,并不是创建一个新的链接,而是从链接池中取出一个已创建的空闲链接对象。使用完毕后,用户也并不是将链接关闭,而是将链接放回链接池中,以供下一个请求访问使用。而链接的创建、断开都由链接池自身来管理。同时,还能够经过设置链接池的参数来控制链接池中的初始链接数、链接的上下限数以及每一个链接的最大使用次数、最大空闲时间等等。也能够经过其自身的管理机制来监视数据库链接的数量、使用状况等。java

注意事项

一、数据库链接池的最小链接数是链接池一直保持的数据库链接,因此若是应用程序对数据库链接的使用量不大,将会有大量的数据库链接资源被浪费。 
二、数据库链接池的最大链接数是链接池能申请的最大链接数,若是数据库链接请求超过此数,后面的数据库链接请求将被加入到等待队列中,这会影响以后的数据库操做。 
三、最大链接数具体值要看系统的访问量.要通过不断测试取一个平衡值 
四、隔一段时间对链接池进行检测,发现小于最小链接数的则补充相应数量的新链接 
五、最小链接数与最大链接数差距,最小链接数与最大链接数相差太大,那么最早的链接请求将会获利,以后超过最小链接数量的链接请求等价于创建一个新的数据库链接。不过,这些大于最小链接数的数据库链接在使用完不会立刻被释放,它将被放到链接池中等待重复使用或是空闲超时后被释放。sql

数据库链接池配置属性

目前数据库链接池种类繁多,不一样种类基本的配置属性大同小异,例如c3p0、Proxool、DDConnectionBroker、DBPool、XAPool、Druid、dbcp,这里咱们以dbcp为例说说主要的配置项:数据库

#最大链接数量:链接池在同一时间可以分配的最大活动链接的数量,,若是设置为非正数则表示不限制,默认值8
maxActive=15
#最小空闲链接:链接池中允许保持空闲状态的最小链接数量,低于这个数量将建立新的链接,若是设置为0则不建立,默认值0
minIdle=5
#最大空闲链接:链接池中允许保持空闲状态的最大链接数量,超过的空闲链接将被释放,若是设置为负数表示不限制,默认值8
maxIdle=10
#初始化链接数:链接池启动时建立的初始化链接数量,默认值0
initialSize=5
#链接被泄露时是否打印
logAbandoned=true
#是否自动回收超时链接
removeAbandoned=true 
#超时时间(以秒数为单位)
removeAbandonedTimeout=180
# 最大等待时间:当没有可用链接时,链接池等待链接被归还的最大时间(以毫秒计数),超过期间则抛出异常,若是设置为-1表示无限等待,默认值无限
maxWait=3000
#在空闲链接回收器线程运行期间休眠的时间值(以毫秒为单位).
timeBetweenEvictionRunsMillis=10000
#在每次空闲链接回收器线程(若是有)运行时检查的链接数量
numTestsPerEvictionRun=8
#链接在池中保持空闲而不被空闲链接回收器线程
minEvictableIdleTimeMillis=10000
#用来验证从链接池取出的链接
validationQuery=SELECT 1
#指明是否在从池中取出链接前进行检验
testOnBorrow=true
#testOnReturn  false  指明是否在归还到池中前进行检验
testOnReturn=true
#设置为true后若是要生效,validationQuery参数必须设置为非空字符串
testWhileIdle

自定义数据库链接池示例

 首先看一下链接池的定义。它经过构造函数初始化链接的最大上限,经过一个双向队列来维护链接,调用方须要先调用fetchConnection(long)方法来指定在多少毫秒内超时获取链接,当链接使用完成后,须要调用releaseConnection(Connection)方法将链接放回线程池ide

public class ConnectionPool {
    private LinkedList<Connection> pool = new LinkedList<Connection>();

    /**
     * 初始化链接池的大小
     * @param initialSize
     */
    public ConnectionPool(int initialSize) {
        if (initialSize > 0) {
            for (int i = 0; i < initialSize; i++) {
                pool.addLast(ConnectionDriver.createConnection());
            }
        }
    }

    /**
     * 释放链接,放回到链接池
     * @param connection
     */
    public void releaseConnection(Connection connection){
        if(connection != null){
            synchronized (pool) {
                // 链接释放后须要进行通知,这样其余消费者可以感知到链接池中已经归还了一个链接
                pool.addLast(connection);
                pool.notifyAll();
            }
        }
    }

    /**
     * 在mills内没法获取到链接,将会返回null
     * @param mills
     * @return
     * @throws InterruptedException
     */
    public Connection fetchConnection(long mills) throws InterruptedException{
        synchronized (pool) {
            // 无限制等待
            if (mills <= 0) {
                while (pool.isEmpty()) {
                    pool.wait();
                }
                return pool.removeFirst();
            }else{
                long future = System.currentTimeMillis() + mills;
                long remaining = mills;
                while (pool.isEmpty() && remaining > 0) {
                    // 等待超时
                    pool.wait(remaining);
                    remaining = future - System.currentTimeMillis();
                }
                Connection result = null;
                if (!pool.isEmpty()) {
                    result = pool.removeFirst();
                }
                return result;
            }
        }
    }
}

因为java.sql.Connection是一个接口,最终的实现是由数据库驱动提供方来实现的,考虑到只是个示例,咱们经过动态代理构造了一个Connection,该Connection的代理实现仅仅是在commit()方法调用时休眠100毫秒函数

public class ConnectionDriver {
    static class ConnectionHandler implements InvocationHandler{
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(method.equals("commit")){
                TimeUnit.MILLISECONDS.sleep(100);
            }
            return null;
        }
    }

    /**
     * 建立一个Connection的代理,在commit时休眠100毫秒
     * @return
     */
    public static final Connection createConnection(){
        return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),
                new Class[] { Connection.class },new ConnectionHandler());
    }
}

下面经过一个示例来测试简易数据库链接池的工做状况,模拟客户端ConnectionRunner获取、使用、最后释放链接的过程,当它使用时链接将会增长获取到链接的数量,反之,将会增长未获取到链接的数量测试

public class ConnectionPoolTest {
    static ConnectionPool pool = new ConnectionPool(10);
    // 保证全部ConnectionRunner可以同时开始
    static CountDownLatch start = new CountDownLatch(1);
    // main线程将会等待全部ConnectionRunner结束后才能继续执行
    static CountDownLatch end;
    public static void main(String[] args) {
        // 线程数量,能够修改线程数量进行观察
        int threadCount = 10;
        end = new CountDownLatch(threadCount);
        int count = 20;
        AtomicInteger got = new AtomicInteger();
        AtomicInteger notGot = new AtomicInteger();
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new ConnetionRunner(count, got, notGot), "ConnectionRunnerThread");
            thread.start();
        }
        start.countDown();
        try {
            end.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("total invoke: " + (threadCount * count));
        System.out.println("got connection: " + got);
        System.out.println("not got connection " + notGot);
    }

    static class ConnetionRunner implements Runnable {

        int count;
        AtomicInteger got;
        AtomicInteger notGot;
        public ConnetionRunner(int count, AtomicInteger got, AtomicInteger notGot) {
            this.count = count;
            this.got = got;
            this.notGot = notGot;
        }
        @Override
        public void run() {
            try {
                start.await();
            } catch (Exception ex) {
            }
            while (count > 0) {
                try {
                    // 从线程池中获取链接,若是1000ms内没法获取到,将会返回null
                    // 分别统计链接获取的数量got和未获取到的数量notGot
                    Connection connection = pool.fetchConnection(1);
                    if (connection != null) {
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConnection(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                    }
                } catch (Exception ex) {
                } finally {
                    count--;
                }
            }
            end.countDown();
        }

    }

}

CountDownLatch类是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞程序继续执行CountDownLatch如其所写,是一个倒计数的锁存器,当计数减至0时触发特定的事件。利用这种特性,可让主线程等待子线程的结束。这这里保证让全部的ConnetionRunner 
都执行完再执行main进行打印。fetch

运行结果: 
20个客户端大数据

total invoke: 200
got connection: 200
not got connection 0

50个客户端ui

total invoke: 1000
got connection: 999
not got connection 1

100个客户端this

total invoke: 2000
got connection: 1842
not got connection 158

在资源必定的状况下(链接池中的10个链接),随着客户端线程的逐步增长,客户端出现超时没法获取链接的比率不断升高。虽然客户端线程在这种超时获取的模式下会出现链接没法获取的状况,可是它可以保证客户端线程不会一直挂在链接获取的操做上,而是“按时”返回,并告知客户端链接获取出现问题,是系统的一种自我保护机制。数据库链接池的设计也能够复用到其余的资源获取的场景,针对昂贵资源(好比数据库链接)的获取都应该加以超时限制。

转载:http://blog.csdn.net/fuyuwei2015/article/details/72419975

相关文章
相关标签/搜索