1. 什么是連接池
一般在程序中如果要和其他的系統(tǒng)創(chuàng)建連接進(jìn)行交互并且連接的創(chuàng)建代價(jià)比較"昂貴"就需要用到連接池. 那怎么樣才算是昂貴呢? 簡(jiǎn)單說來就是創(chuàng)建連接的時(shí)間接近甚至超過交互的時(shí)間. 所以連接池就是一個(gè)創(chuàng)建連接管理連接, 對(duì)連接進(jìn)行緩存的技術(shù). 最常見的連接池就是數(shù)據(jù)庫(kù)連接池. 更加具體的關(guān)于連接池的介紹, 請(qǐng)移步連接池
2. Jedis的連接池
既然連接池的作用就是管理連接, 那Jedis的連接池也不例外, 它的作用就是緩存Jedis和redis server之間的連接. Jedis 連接池的作用具體來說分為以下幾個(gè)部分:
2.1 創(chuàng)建保存連接的容器
當(dāng)初始化一個(gè)JedisPool的時(shí)候會(huì)創(chuàng)建一個(gè)LinkedBlockingDeque<PooledObject<T>> idleObjects隊(duì)列, 這個(gè)隊(duì)列用于存放已經(jīng)和redis server建立連接********并且已經(jīng)使用過********的連接對(duì)象(實(shí)際上存放的是DefaultPooledObject<Jedis>對(duì)象, 后面會(huì)看到).
同時(shí)還會(huì)創(chuàng)建一個(gè)Map<IdentityWrapper<T>, PooledObject<T>> allObjects對(duì)象, 這個(gè)Map用于沒有可用的連接時(shí)新創(chuàng)建出來的連接.
2.2 發(fā)起請(qǐng)求獲取連接
當(dāng)發(fā)起請(qǐng)求從連接池中獲取一個(gè)連接的時(shí)候, 連接池會(huì)先從idleObjects隊(duì)列中獲取連接, 如果獲取不到則開始創(chuàng)建一個(gè)新的連接, 創(chuàng)建新的連接是首先判斷當(dāng)前已經(jīng)存在的連接是否已經(jīng)大于連接池可容納的連接數(shù)量, 如果是則不予創(chuàng)建, 以阻塞等的方式從idleObjects中等待獲取可用連接(默認(rèn)是阻塞不超時(shí)等待即等待直到有可用的連接, 但是也可以配置超時(shí)時(shí)間). 如果可以創(chuàng)建, 則創(chuàng)建一個(gè)新的連接放入到allObjects對(duì)象中, 同時(shí)將連接返回.
2.3 連接使用完畢關(guān)閉連接
當(dāng)使用完連接調(diào)用連接關(guān)閉的時(shí)候, 連接池會(huì)將歸還連接從allObjects中拿出來放入到idleObjects中, 所以下一次再獲取連接將從idleObjects直接獲取.
但是這里要特別注意, 從allObjects中拿出來放入到idleObjects中時(shí), 并沒有將連接從allObjects中刪除, 也就是說allObjects和idleObjects中的連接實(shí)際上是指向同一個(gè)對(duì)象實(shí)例的. 為什么要這么做, 而不是直接刪除allObjects中的連接呢? 因?yàn)?code>JedisPool會(huì)在指定的時(shí)間內(nèi)對(duì)連接池中空閑對(duì)象進(jìn)行刪除, 這樣可以減少資源的占用, 這個(gè)是JedisPool的單獨(dú)線程自動(dòng)完成的操作. 所以說, 如果有個(gè)連接創(chuàng)建出來長(zhǎng)時(shí)間沒有使用是會(huì)被自動(dòng)銷毀的, 而不是一直連接著占用資源.
3. 源碼閱讀
下面針對(duì)于上面提到的Jedis Pool的關(guān)鍵點(diǎn)來看看具體的代碼實(shí)現(xiàn)
3.1 連接池的創(chuàng)建
JedisPool pool = new JedisPool(new JedisPoolConfig(), hnp.getHost(), hnp.getPort(), 2000);
調(diào)用上面的代碼就可以創(chuàng)建一個(gè)連接池了, 其中最關(guān)鍵的部分就是JedisPoolConfig對(duì)象的創(chuàng)建
// JedisPoolConfig.java
public class JedisPoolConfig extends GenericObjectPoolConfig {
public JedisPoolConfig() {
...
// 連接空閑的最小時(shí)間, 達(dá)到此值后空閑連接將會(huì)被移除. 負(fù)值(-1)表示不移除
setMinEvictableIdleTimeMillis(60000);
// "空閑鏈接"檢測(cè)線程, 檢測(cè)的周期, 毫秒數(shù). 如果為負(fù)值, 表示不運(yùn)行“檢測(cè)線程”. 默認(rèn)為-1
setTimeBetweenEvictionRunsMillis(1000);
...
}
}
在創(chuàng)建連接池的同時(shí)會(huì)創(chuàng)建idleObjects對(duì)象
// GenericObjectPool.java
public GenericObjectPool(PooledObjectFactory<T> factory,
GenericObjectPoolConfig config) {
...other code...
// 創(chuàng)建idleObjects對(duì)象
idleObjects = new LinkedBlockingDeque<PooledObject<T>>(config.getFairness());
setConfig(config);
startEvictor(getTimeBetweenEvictionRunsMillis());
}
在上面的代碼中創(chuàng)建了idleObjects對(duì)象, 但是還有一句很關(guān)鍵的代碼startEvictor(getTimeBetweenEvictionRunsMillis());.
沒錯(cuò),這就是啟動(dòng)自動(dòng)回收空閑連接的代碼!
// BaseGenericObjectPool.java
final void startEvictor(long delay) {
synchronized (evictionLock) {
if (null != evictor) {
EvictionTimer.cancel(evictor);
evictor = null;
evictionIterator = null;
}
if (delay > 0) {
evictor = new Evictor();
EvictionTimer.schedule(evictor, delay, delay);
}
}
}
至于保存剛創(chuàng)建出來的連接的allObjects對(duì)象是GenericObjectPool的成員變量
private final Map<IdentityWrapper<T>, PooledObject<T>> allObjects =
new ConcurrentHashMap<IdentityWrapper<T>, PooledObject<T>>();
3.2 獲取一個(gè)連接
Jedis jedis = pool.getResource();
調(diào)用上面的代碼就可以獲取一個(gè)連接了
獲取連接最關(guān)鍵的就是borrowObject
// GenericObjectPool.java
public T borrowObject(long borrowMaxWaitMillis) throws Exception {
assertOpen();
...other code...
PooledObject<T> p = null;
// 沒有空閑連接的時(shí)候是否阻塞的等待連接
boolean blockWhenExhausted = getBlockWhenExhausted();
boolean create;
long waitTime = System.currentTimeMillis();
while (p == null) {
create = false;
// 沒有空閑連接時(shí)等待
if (blockWhenExhausted) {
// 先從idleObjects隊(duì)列中獲取
p = idleObjects.pollFirst();
if (p == null) {
// 如果隊(duì)列中沒有空閑的連接, 則創(chuàng)建一個(gè)連接
p = create();
if (p != null) {
create = true;
}
}
// 如果連接創(chuàng)建失敗, 則繼續(xù)從idleObjects中阻塞的獲取連接
if (p == null) {
if (borrowMaxWaitMillis < 0) {
// 無(wú)限制的等待, 不會(huì)超時(shí)
p = idleObjects.takeFirst();
} else {
// 有超時(shí)時(shí)間的等待
p = idleObjects.pollFirst(borrowMaxWaitMillis,
TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException(
"Timeout waiting for idle object");
}
if (!p.allocate()) {
p = null;
}
} else { // 沒有空閑連接時(shí)直接拋出異常
p = idleObjects.pollFirst();
if (p == null) {
p = create();
if (p != null) {
create = true;
}
}
if (p == null) {
throw new NoSuchElementException("Pool exhausted");
}
if (!p.allocate()) {
p = null;
}
}
...other code...
}
updateStatsBorrow(p, System.currentTimeMillis() - waitTime);
// 返回連接
return p.getObject();
}
上面是獲取連接時(shí)的一個(gè)完整的流程, 包括有空閑連接時(shí)直接返回, 沒有空閑連接時(shí)創(chuàng)建空閑連接, 連接創(chuàng)建失敗后是繼續(xù)阻塞等待(包括是否超時(shí))還是直接拋出異常. 下面看一下是如何創(chuàng)建一個(gè)連接的
// GenericObjectPool.java
private PooledObject<T> create() throws Exception {
// 判斷當(dāng)前已經(jīng)創(chuàng)建的連接是否已經(jīng)超過設(shè)置的最大連接數(shù)(默認(rèn)是8)
int localMaxTotal = getMaxTotal();
long newCreateCount = createCount.incrementAndGet();
if (localMaxTotal > -1 && newCreateCount > localMaxTotal ||
newCreateCount > Integer.MAX_VALUE) {
createCount.decrementAndGet();
return null;
}
final PooledObject<T> p;
try {
// 創(chuàng)建一個(gè)到redis server的連接
p = factory.makeObject();
} catch (Exception e) {
createCount.decrementAndGet();
throw e;
}
...other code...
createdCount.incrementAndGet();
// 將新創(chuàng)建的連接放入到allObjects中
allObjects.put(new IdentityWrapper<T>(p.getObject()), p);
// 返回新創(chuàng)建的連接
return p;
}
上面就是創(chuàng)建一個(gè)到redis server連接的過程. 但是上面沒有深究是如何與redis server創(chuàng)建連接的, 因?yàn)檫@次介紹的主題是JedisPool, 所以客戶端與redis server創(chuàng)建連接的具體細(xì)節(jié)會(huì)在之后的文章中介紹.
3.3 關(guān)閉一個(gè)連接
在使用完一個(gè)連接之后就要將一個(gè)連接關(guān)閉. 其實(shí)上面創(chuàng)建一個(gè)連接之后還有一個(gè)比較重要的步驟
@Override
public Jedis getResource() {
// 獲取連接
Jedis jedis = super.getResource();
// 將連接池放入到連接中, 這里這么做的目的其實(shí)就是為關(guān)閉連接的時(shí)候作準(zhǔn)備的
jedis.setDataSource(this);
return jedis;
}
調(diào)用下面的代碼就可以關(guān)閉一個(gè)連接了
jedis.close();
我們知道連接池的作用就是為了緩存連接而生的, 所以這里的關(guān)閉連接肯定不能是直接和redis server斷開連接, 所以讓我們看看這里的關(guān)閉連接到底是做了什么操作從而實(shí)現(xiàn)連接的復(fù)用
// Jedis.java
public void close() {
// 如果連接池存在就調(diào)用連接池的返回資源(這里的資源就是連接)的方法
if (dataSource != null) {
if (client.isBroken()) {
this.dataSource.returnBrokenResource(this);
} else {
this.dataSource.returnResource(this);
}
} else { // 如果連接池不存在就直接關(guān)閉連接
client.close();
}
}
所以當(dāng)關(guān)閉一個(gè)連接的時(shí)候如果連接存在其實(shí)是將資源還給了連接池. 其中最核心的方法就是returnObject
// GenericObjectPool.java
@Override
public void returnObject(T obj) {
// 從allObjects中獲取要?dú)w還的連接
PooledObject<T> p = allObjects.get(new IdentityWrapper<T>(obj));
...other code...
int maxIdleSave = getMaxIdle();
// 如果idleObjects隊(duì)列中連接的數(shù)據(jù)已經(jīng)>=允許的最大連接數(shù)或者連接池已經(jīng)關(guān)閉就直接銷毀這個(gè)連接
if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
try {
destroy(p);
} catch (Exception e) {
swallowException(e);
}
} else { // 將連接放入到idleObjects隊(duì)列中, 一旦將連接放入到idleObjects中如果連接長(zhǎng)時(shí)間不被使用就會(huì)被自動(dòng)回收
if (getLifo()) { // 默認(rèn)是使用last in first out機(jī)制
idleObjects.addFirst(p);
} else {
idleObjects.addLast(p);
}
if (isClosed()) {
// Pool closed while object was being added to idle objects.
// Make sure the returned object is destroyed rather than left
// in the idle object pool (which would effectively be a leak)
clear();
}
}
updateStatsReturn(activeTime);
}
4. 總結(jié)
- 使用了這么久的連接池自從看了
Jedis Pool的源碼之后才對(duì)連接池有了一個(gè)直觀的認(rèn)識(shí), 之后可以看看數(shù)據(jù)庫(kù)的連接池, 比較一下兩個(gè)對(duì)于連接池實(shí)現(xiàn)的異同 -
Jedis的連接池使用上是對(duì)apache common pool2的一個(gè)實(shí)現(xiàn), 有了Jedis Pool這個(gè)例子以后要是要實(shí)現(xiàn)自己的連接池也方便許多