druid 连接池源码分析

一 java 对数据库的支持

java.sql 包的支持,一般使用显示编程的方式。 connection接口、statment接口、ResultSet接口、DriverManager类。

image.png

JDBC例子


 Connection con = null; //表示数据库的连接对象 

  PreparedStatement pstmt = null; //表示数据库更新操作 

  ResultSet result = null; 

  String like_name ="Tom1"; 

  intlike_age = 12; 

  String sql = "select name,age,birthday from java_study.person where name like ? or age = ?"; 

  Class.forName(DBDRIVER); //1、使用CLASS 类加载驱动程序 

  System.out.println(sql); 

  con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、连接数据库 

  pstmt = con.prepareStatement(sql); //使用预处理的方式创建对象 

  pstmt.setString(1, "%"+like_name+"%"); 

  pstmt.setInt(2, like_age); 

  result = pstmt.executeQuery(); //执行SQL 语句,更新数据库 

  while(result.next()){ 

      String name = result.getString("name"); 

      intage = result.getInt("age"); 

      Date date = result.getDate("birthday"); 

      System.out.println(name+","+age+","+date); 

  } 

          result.close();

         pstmt.close();

con.close(); // 4、关闭数据库

JDBC事务例子

public void JdbcTransfer() { 
    java.sql.Connection conn = null;
     try{ 
        conn = conn =DriverManager.getConnection("jdbc:oracle:thin:@host:1521:SID","username","userpwd");
         // 将自动提交设置为 false,
         //若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
         conn.setAutoCommit(false);
 
         stmt = conn.createStatement(); 
         // 将 A 账户中的金额减少 500 
         stmt.execute("\
         update t_account set amount = amount - 500 where account_id = 'A'");
         // 将 B 账户中的金额增加 500 
         stmt.execute("\
         update t_account set amount = amount + 500 where account_id = 'B'");
 
         // 提交事务
         conn.commit();
         // 事务提交:转账的两步操作同时成功
     } catch(SQLException sqle){            
         try{ 
             // 发生异常,回滚在本事务中的操做
            conn.rollback();
             // 事务回滚:转账的两步操作完全撤销
             stmt.close(); 
             conn.close(); 
         }catch(Exception ignore){ 
 
         } 
         sqle.printStackTrace(); 
     } 
}

javax.sql包的支持
Java.sql.*
包含的接口和类采用传统的C/S体系结构设计思想.主要功能针对基本数据库编程服务,如生成连接,执行语句以及准备语句和运行批处理语句.也有一些高级功能如批处理更新,可滚动结果集,事务隔离以及SQL数据类型.
javax.sql.*
引入了JDBC编程方面一些主要的体系结构改变,并且为连接管理,分布式事务处理和老式连接提供了更好的抽象.这个包也引入了容器管理的连接缓冲池,分布式事务以及行集(rowset).
java.sql.是jdbc2.0之前的东西
javax.sql.
包括了jdbc3.0的特性

javax.sql.提供了很多新特性,是对java.sql的补充,具体提供了一下方面的功能
(1)Datasource接口提供了一种可选择性的方式去建立连接
(2)提供了连接池的支持
(3)增加了分布式的事务处理机制
(4)增加了rowset
(注意javax.sql.
并不是包含java.sql.*,它俩一起组成了访问数据的类)

在javax中我们一般要关注的点是:数据源对象、连接池技术、分布式事务,在java只定义了接口,没有实际的应用,所以业内有很多的对javax.sql的实现。
DataSrouce接口(数据源对象)、PooledConnection接口(池技术)、XAConnection(分布式事务),这三个接口是jdbc的新特性新规范。
所以一般来说在各个厂商的实现来看,pooled一般指连接池,XAxxxx的只分布式。
如果要分析一个数据源的源码的话,从这三个方面来看:


image.png

2 druid源码分析
1 druid是由数组实现的
一个数据库连接池的开源软件。所以connection连接池的初始化、创建、回收、收缩、获取,都是关键点。


image.png

通过继承树可以看到继承了好的类和接口,其中主要的有两个线,DruidAbstractDataSource和CommonDataSource。说明DruidDataSrouce是一个DataSource,可以getConnection获取连接。


image.png

初始化时在获取连接的时候进行的,如果没有手动init的话。


image.png

二 锁、condition、创建\销毁线程
在druidDataSource中有一个重入锁,衍生两个condition,一个监控连接池是否为空,一个监控连接池不为空。
在该类中有两个线程,一个生成连接,一个回收连接。在创建、获取、回收的时候都会使用这些锁和condition。

image.png

由于数据连接数组是公共资源,所以在多线程并行的情况下,要加锁使用。
而在用户线程发现连接池中没有资源之后就会与创建连接的线程进行通信。

druid底层是使用数组实现的。
获取连接逻辑:

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
    if (closed) {
        connectErrorCount.incrementAndGet();
        throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
    }
 
    if (!enable) {
        connectErrorCount.incrementAndGet();
        throw new DataSourceDisableException();
    }
 
    final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
    final int maxWaitThreadCount = getMaxWaitThreadCount();
 
    DruidConnectionHolder holder;
    try {
        lock.lockInterruptibly(); // 获取锁,所有对数组可能的操作都要进行加锁
    } catch (InterruptedException e) {
        connectErrorCount.incrementAndGet();
        throw new SQLException("interrupt", e);
    }
 
    try {
        if (maxWaitThreadCount > 0) {
            if (notEmptyWaitThreadCount >= maxWaitThreadCount) {
                connectErrorCount.incrementAndGet();
                throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
                                       + lock.getQueueLength());
            }
        }
 
        connectCount++;
 
        if (maxWait > 0) {
            holder = pollLast(nanos);
        } else {
            holder = takeLast();
        }
 
        if (holder != null) {
            activeCount++;
            if (activeCount > activePeak) {
                activePeak = activeCount;
                activePeakTime = System.currentTimeMillis();
            }
        }
    } catch (InterruptedException e) {
        connectErrorCount.incrementAndGet();
        throw new SQLException(e.getMessage(), e);
    } catch (SQLException e) {
        connectErrorCount.incrementAndGet();
        throw e;
    } finally {
        lock.unlock();
    }
 
    if (holder == null) {
        long waitNanos = waitNanosLocal.get();
 
        StringBuilder buf = new StringBuilder();
        buf.append("wait millis ")//
        .append(waitNanos / (1000 * 1000))//
        .append(", active " + activeCount)//
        ;
 
        List<JdbcSqlStatValue> sqlList = this.getDataSourceStat().getRuningSqlList();
        for (int i = 0; i < sqlList.size(); ++i) {
            if (i != 0) {
                buf.append('\n');
            } else {
                buf.append(", ");
            }
            JdbcSqlStatValue sql = sqlList.get(i);
            buf.append("runningSqlCount ");
            buf.append(sql.getRunningCount());
            buf.append(" : ");
            buf.append(sql.getSql());
        }
 
        String errorMessage = buf.toString();
 
        if (this.createError != null) {
            throw new GetConnectionTimeoutException(errorMessage, createError);
        } else {
            throw new GetConnectionTimeoutException(errorMessage);
        }
    }
 
    holder.incrementUseCount();
 
    DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
    return poolalbeConnection;
}

创建线程逻辑:

lock.lock();
try {
    connections[poolingCount++] = holder;
 
    if (poolingCount > poolingPeak) {
        poolingPeak = poolingCount;
        poolingPeakTime = System.currentTimeMillis();
    }
 
    errorCount = 0; // reset errorCount
 
    notEmpty.signal();
    notEmptySignalCount++;
} finally {
    lock.unlock();
}

回收逻辑:

// 回收方法是在DruidPooledConnection中的close方法中调用的,DruidPooledConnection是connection的子类,所以调用方使用框架时,使用close方法就是回收操作。
lock.lockInterruptibly();
try {
    activeCount--;
    closeCount++;
 
    putLast(holder, lastActiveTimeMillis);
    recycleCount++;
} finally {
    lock.unlock();
}

收缩逻辑:

public void shrink(boolean checkTime) {
    final List<DruidConnectionHolder> evictList = new ArrayList<DruidConnectionHolder>();
    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];
 
            if (checkTime) {
                long idleMillis = currentTimeMillis - connection.getLastActiveTimeMillis();  // 根据timeBetweenEvictionRunsMillis进行判断
                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();
    }
}

3 数据源的配置

4 druid的代理与责任链


image.png

在DruidDataSource中创建的connection是druid自己实现connection接口的connectionProxyImpl类。是个代理类,代理的是connection的实现类,有各个开发商实现的具体的类。

image.png

一个使用driver生成真实的连接,一个对真实的连接进行封装成connectionProxy。
而在ds中会把这个代理类包装到holder中,在使用连接池的时候,获取holder,从holder中获取代理连接,在proxy中获取statment,从而执行sql,处理返回数据。

image.png

实际上,druid的对各个sql的接口都进行了代理,进行实现统计或者功能。通过源码可以看到,如果配置了责任链的节点就会使用代理类,如果没有使用则不会使用代理。

image.png

可以看出,druid的监控统计是使用责任链模式与代理模式实现的。代理类的作用是调用过滤责任链。

image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,293评论 18 399
  • 一. Java基础部分.................................................
    wy_sure阅读 3,731评论 0 11
  • JAVA面试题 1、作用域public,private,protected,以及不写时的区别答:区别如下:作用域 ...
    JA尐白阅读 1,124评论 1 0
  • 好久没在简书里写东西了。 今天忽然下起了雨,初夏的雨竟然给了我春雨的感觉,我在心中默默的惊奇。 从图书馆回宿舍的路...
    onematchleft阅读 446评论 0 0
  • 一个小龙女版转世灵童的成长故事:她是黑暗的信徒,直到黑肤黑眼的法师雀鹰把光指给她看。 “青青草坪上兔子哀鸣死去 ,...
    一条污蚣阅读 365评论 0 0