今天我们一起来看下Druid。
准备工作
先将Druid的源码down下来,传送门:https://github.com/alibaba/druid/tree/1.2.8 ,如果是国内用户可能会比较慢,我反正是这样,推荐使用国内镜像
git clone https://gitclone.com/github.com/alibaba/druid.git //域名中间加上gitclone.com
mvn clean install -DskipTests
开始阅读
Druid作为一个数据库连接池框架,提供了很强大的性能和监控。作为一个数据库框架其实核心就是对数据库连接的各种操作,在连接上进行各种封装。
所以我们先从pool包的 DruidDataSource 类开始。通过构造方法可以看出,连接池默认使用的非公平锁策略,因为效率会更高。
这里可以看到有一个super方法,是父类DruidAbstractDataSource 构造器里实现的,具体做了什么呢?其实很简单,就是初始化了一个锁,和两个condition。那后面其实会用到锁去控制资源的访问。
然后下面configFromPropety()方法看名字就知道是对设置的属性进行赋值,毕竟这个类里面定义了那么多成员变量,我们进去确认下。
和我猜的一样,只是这种两个中括号的格式和我们日常代码有点不太一样,第一眼看着有点奇怪。
我们通过框架的测试用例可以看出类DruidDataSource 构造之后,调用的入口其实是init()方法。
我们来详细看下init()这个方法:
public void init() throws SQLException {
//如果已经初始化则直接返回
if (inited) {
return;
}
// bug fixed for dead lock, for issue #2980
DruidDriver.getInstance();
final ReentrantLock lock = this.lock;
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
throw new SQLException("interrupt", e);
}
boolean init = false;
try {
if (inited) {
return;
}
initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());
this.id = DruidDriver.createDataSourceId();
if (this.id > 1) {
long delta = (this.id - 1) * 100000;
this.connectionIdSeedUpdater.addAndGet(this, delta);
this.statementIdSeedUpdater.addAndGet(this, delta);
this.resultSetIdSeedUpdater.addAndGet(this, delta);
this.transactionIdSeedUpdater.addAndGet(this, delta);
}
if (this.jdbcUrl != null) {
this.jdbcUrl = this.jdbcUrl.trim();
initFromWrapDriverUrl();
}
for (Filter filter : filters) {
filter.init(this);
}
if (this.dbTypeName == null || this.dbTypeName.length() == 0) {
this.dbTypeName = JdbcUtils.getDbType(jdbcUrl, null);
}
DbType dbType = DbType.of(this.dbTypeName);
if (dbType == DbType.mysql
|| dbType == DbType.mariadb
|| dbType == DbType.oceanbase
|| dbType == DbType.ads) {
boolean cacheServerConfigurationSet = false;
if (this.connectProperties.containsKey("cacheServerConfiguration")) {
cacheServerConfigurationSet = true;
} else if (this.jdbcUrl.indexOf("cacheServerConfiguration") != -1) {
cacheServerConfigurationSet = true;
}
if (cacheServerConfigurationSet) {
this.connectProperties.put("cacheServerConfiguration", "true");
}
}
if (maxActive <= 0) {
throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
if (maxActive < minIdle) {
throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
if (getInitialSize() > maxActive) {
throw new IllegalArgumentException("illegal initialSize " + this.initialSize + ", maxActive " + maxActive);
}
if (timeBetweenLogStatsMillis > 0 && useGlobalDataSourceStat) {
throw new IllegalArgumentException("timeBetweenLogStatsMillis not support useGlobalDataSourceStat=true");
}
if (maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis) {
throw new SQLException("maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis");
}
if (this.driverClass != null) {
this.driverClass = driverClass.trim();
}
//初始化SPI相关配置
initFromSPIServiceLoader();
//根据对应驱动类创建驱动,里面增加了对MockDriver的创建
resolveDriver();
//对oracle和db2的一些常规错误进行了校验
initCheck();
//分类初始化驱动
initExceptionSorter();
//初始化连接有效性验证器
initValidConnectionChecker();
//验证查询
validationQueryCheck();
//判断是否适用全局DataSourceStat,如果不是应该后续什么地方可以自定义datasource
if (isUseGlobalDataSourceStat()) {
dataSourceStat = JdbcDataSourceStat.getGlobal();
if (dataSourceStat == null) {
dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbTypeName);
JdbcDataSourceStat.setGlobal(dataSourceStat);
}
if (dataSourceStat.getDbType() == null) {
dataSourceStat.setDbType(this.dbTypeName);
}
} else {
dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbTypeName, this.connectProperties);
}
dataSourceStat.setResetStatEnable(this.resetStatEnable);
connections = new DruidConnectionHolder[maxActive];
evictConnections = new DruidConnectionHolder[maxActive];
keepAliveConnections = new DruidConnectionHolder[maxActive];
SQLException connectError = null;
//分同步和异步两种情况创建连接任务
if (createScheduler != null && asyncInit) {
for (int i = 0; i < initialSize; ++i) {
submitCreateTask(true);
}
} else if (!asyncInit) {
// init connections
while (poolingCount < initialSize) {
try {
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
connections[poolingCount++] = holder;
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
if (initExceptionThrow) {
connectError = ex;
break;
} else {
Thread.sleep(3000);
}
}
}
if (poolingCount > 0) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
}
//创建日志线程
createAndLogThread();
createAndStartCreatorThread();
createAndStartDestroyThread();
initedLatch.await();
init = true;
initedTime = new Date();
//注册Mbean
registerMbean();
if (connectError != null && poolingCount == 0) {
throw connectError;
}
if (keepAlive) {
// async fill to minIdle
if (createScheduler != null) {
for (int i = 0; i < minIdle; ++i) {
submitCreateTask(true);
}
} else {
this.emptySignal();
}
}
} catch (SQLException e) {
LOG.error("{dataSource-" + this.getID() + "} init error", e);
throw e;
} catch (InterruptedException e) {
throw new SQLException(e.getMessage(), e);
} catch (RuntimeException e){
LOG.error("{dataSource-" + this.getID() + "} init error", e);
throw e;
} catch (Error e){
LOG.error("{dataSource-" + this.getID() + "} init error", e);
throw e;
} finally {
inited = true;
lock.unlock();
if (init && LOG.isInfoEnabled()) {
String msg = "{dataSource-" + this.getID();
if (this.name != null && !this.name.isEmpty()) {
msg += ",";
msg += this.name;
}
msg += "} inited";
LOG.info(msg);
}
}
}
上面第九行代码,看着很奇怪,又没有变量去接收,那执行它有啥用呢?其实就是为了执行对应类里面静态代码块的驱动注册而已。当Thread-1执行init()方法时,这时候会锁住DruidDriver,然后一直持有DriverManage,因为需要DriverManage注册驱动,如果Thread-2这时候执行init()方法,若Thread-2被interrupted,则会抛出SQLException异常,会调用DriverManager静态代码块里的loadInitialDrivers()方法,会一直持有DruidDriver,锁住DriverManager,两者互相等待造成死锁。
然后官方的解决方案也很简单,既然是因为初始化静态代码块的时候出现死锁,那就在加锁之前初始化完成,后续即使在加载类也不会加载静态代码块了。
后面就是一些基础值的获取和各种错误的检测,各种配置和驱动设置,比如SPI和各个数据库JDBC驱动等,一些关键地方我都有写注释,然后这个类最核心的地方应该是在创建连接那个地方,有很多复杂的判断和各种计数,这块我现在也没太看懂,等明天调试下在记录下。