Jedis源碼閱讀之連接池

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的連接池也不例外, 它的作用就是緩存Jedisredis 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中刪除, 也就是說allObjectsidleObjects中的連接實(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)自己的連接池也方便許多
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • 1 Redis介紹1.1 什么是NoSql為了解決高并發(fā)、高可擴(kuò)展、高可用、大數(shù)據(jù)存儲(chǔ)問題而產(chǎn)生的數(shù)據(jù)庫(kù)解決方...
    克魯?shù)吕?/span>閱讀 5,726評(píng)論 0 36
  • 1.1 資料 ,最好的入門小冊(cè)子,可以先于一切文檔之前看,免費(fèi)。 作者Antirez的博客,Antirez維護(hù)的R...
    JefferyLcm閱讀 17,320評(píng)論 1 51
  • 心的尖端劃過什么 手的風(fēng)與眼的黑 誰(shuí)說的傳說 我會(huì)在時(shí)光中愛你 愿這隔了千年的癡情 在透過你眼角看到的明月中流連 ...
    Longczx閱讀 274評(píng)論 2 3

友情鏈接更多精彩內(nèi)容