Druid源码解析(一):项目简述与入口代码分析


一、项目介绍

  1、Druid简介

  Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。

    Github项目地址 https://github.com/alibaba/druid 

    文档 https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98 

    下载 http://repo1.maven.org/maven2/com/alibaba/druid/ 

    监控DEMO http://120.26.192.168/druid/index.html

  2、竞品对比

功能类别 功能 Druid HikariCP DBCP Tomcat-jdbc C3P0
性能 PSCache
LRU
SLB负载均衡支持
稳定性 ExceptionSorter
扩展 扩展 Filter     JdbcIntercepter  
监控 监控方式 jmx/log/http jmx/metrics jmx jmx jmx
支持SQL级监控
Spring/Web关联监控
  诊断支持 LogFilter
连接泄露诊断 logAbandoned
安全 SQL防注入
支持配置加密

  3、模块划分

  源码里模块描述:

    filter:增加自定义的扩展能力

    mock:测试模块,可以使用mock模块做一些模拟测试

    pool:核心,入口是DruidDataSource;

    proxy:代理层

    sql:负责sql解析的工作

    stat:扩展能力实现,例如基于filter的监控,真正的实现在stat文件夹

    support:一些附加功能,比如Json解析等

    util:工具类

    wall:防火墙相关,防止sql注入等操作,但是实际上对于现在的项目,对于sql注入都在网关层做了处理,不会真正到数据库连接池层面在做处理。

  另外,Druid还提供了druid-spring-boot-starter,可以快速在Spring-boot中集成。

           

二、快速概览DruidDataSource数据结构

  pool是Druid中最核心的目录,而DruidDataSource是pool中最关键的类之一,其承载了连接池的启动、关闭、以及连接的获取和管理等功能。

  DruidDataSource继承了DruidAbstractDataSource,两个类内部有大量的变量,用来设置连接池的各种参数。

  关键的的数据结构如下表:

name

type

说明

connections

volatile DruidConnectionHolder[]

pool的关键数组,存放连接,实际上是DruidConnectionHolder的数组。Connection由DruidConnectionHolder持有

evictConnections

DruidConnectionHolder[]

被驱逐的Connection的pool,调用收缩方法shrink之后,被收缩的连接都会进入这个数组。

keepAliveConnections

DruidConnectionHolder[]

收缩方法shrink中,满足keepalive状态的连接都进入这个数组。

autoFilters

static List

这个list存全部的filter

enable

volatile boolean

默认值为true,标识连接池是否可用,调用close方法设置该值为false,当为false的时候,连接的error次数增加1,get连接或者其他操作会失败。

inited

volatile boolean

默认值为false,初始化完成的标识。

closing

volatile boolean

关闭过程中的状态。正在close

closed

volatile boolean

关闭完成的状态。

  连接池最关键的数据结构是内部持有DruidConnectionHolder的数组,connections。 DruidConnectionHolder的数据结构为:

name

type

说明

dataSource

final DruidAbstractDataSource

指向DataSource的指针。

conn

final Connection

指向真正的数据库连接,由数据库驱动实现。

connectionId

final long

连接编号。

connectionEventListeners

final List

连接事件监听器。

statementEventListeners

final List

statement事件监听器。

statementPool

PreparedStatementPool

其内部是一个LRUCache,对Statement做缓存。

statementTrace

final List

一个对Statement进行追踪的list,这个statementTrace的作用后面需要详细看看。

三、DruidDataSource入口概览

  连接池在使用时最主要就是获取连接,然后使用连接查询操作数据库,那么获取连接基本上就可以作为连接池的一个入口。

  DruidConnectionHolder是连接池中物理连接的载体,在DruidDataSource中,获取连接的getConnection方法,拿到的是DruidPooledConnection。

    @Override
    public DruidPooledConnection getConnection() throws SQLException {
        return getConnection(maxWait);
    }

    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);
        }
    }

  从上述代码可以看到,其首先调用了 init() 方法对连接池做了初始化,然后从连接池中获取连接,获取连接实际调用的是 getConnectionDirect() 方法,在该方法中调用了 getConnectionInternal() 方法获取的连接

    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("get connection timeout retry : " + notFullTimeoutRetryCnt);
                    }
                    continue;
                }
                throw ex;
            }
......

  在getConnectionInternal() 方法中做了一系列的校验,然后根据不同的条件创建一个具体的连接 DruidConnectionHolder,最终根据 DruidConnectionHolder 获得连接池中的连接并返回。

        holder.incrementUseCount();

        DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
        return poolalbeConnection;

  从上面的代码可以看到,实际是调用了入参为 DruidConnectionHolder 的构造函数 DruidPooledConnection

    ......protected            TransactionInfo       transactionInfo;
    ......public DruidPooledConnection(DruidConnectionHolder holder){
        super(holder.getConnection());

        this.conn = holder.getConnection();
        this.holder = holder;
        this.lock = holder.lock;
        dupCloseLogEnable = holder.getDataSource().isDupCloseLogEnable();
        ownerThread = Thread.currentThread();
        connectedTimeMillis = System.currentTimeMillis();
    }

  实际上DruidPooledConnection内部持有了一个DruidConnectionHolder。 DruidPooledConnection的数据结构如下表:

name

type

说明

conn

Connection

指向真实的数据库连接。

holder

volatile DruidConnectionHolder

指向DruidConnectionHolder。

transactionInfo

TransactionInfo

事务相关的信息

  上述几个关键的类,其相互关系如下图:

        

  个人感觉,DruidConnectionHolder与DruidPooledConnection,实际上是对连接进行了分层。将频繁变更的内容抽象到了DruidConnectionHolder类。 而DruidPooledConnection则存放了Statement的的缓存pool。