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。
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~