從源碼角度看JedisPoolConfig參數配置

作一個積極的人java

編碼、改bug、提高本身node

我有一個樂園,面向編程,春暖花開!redis

你好,JedisPoolConfig

Java中使用Jedis做爲鏈接Redis的工具。在使用Jedis的也能夠配置JedisPool鏈接池,JedisPool配置參數大部分是由JedisPoolConfig的對應項來賦值的。本文簡單總結幾個經常使用的配置,而後經過源碼(版本jedis-3.1.0)的角度讓你理解配置這些參數的原理。數據庫

首先了解一下池化((對象池、數據庫鏈接池、線程池等等))的一些思想和好處。方便後面對JedisPoolConfig的配置的理解。apache

池化的基本思想編程

一、能夠在初始化的時候建立一些對象,當有須要使用的時候不直接從池中獲取,提升響應速度;數組

二、使用過的對象不進行銷燬,保存起來,等下一次須要對象的時候,拿出來重複使用,減小頻繁建立對象所形成的開銷;安全

三、建立的對象統一保存,方面管理和維護。服務器

池化好處總結網絡

一、提升響應的速度

二、下降資源的消耗

三、方便管理和維護

JedisPoolConfig配置說明

類圖和源碼解析

首先看一下類圖:

UTOOLS1571543704493.png

  • BaseGenericObjectPool:封裝公共的配置的參數。
private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
	// DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L
    private long minEvictableIdleTimeMillis =
            DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;

	// DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L
 	private long timeBetweenEvictionRunsMillis =
            DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;

	// DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3
    private int numTestsPerEvictionRun =
            DEFAULT_NUM_TESTS_PER_EVICTION_RUN;

	// DEFAULT_TEST_ON_CREATE = false
    private boolean testOnCreate = DEFAULT_TEST_ON_CREATE;
	// DEFAULT_TEST_ON_BORROW = false
    private boolean testOnBorrow = DEFAULT_TEST_ON_BORROW;
	// DEFAULT_TEST_ON_RETURN = false
    private boolean testOnReturn = DEFAULT_TEST_ON_RETURN;
	// DEFAULT_TEST_WHILE_IDLE = false
    private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE;
	//...
}
複製代碼
  • GenericObjectPoolConfig:繼承BaseGenericObjectPool,內部代碼很簡單,封裝了GenericObjectPool的配置。主要是maxTotalmaxIdleminIdle此類不是線程安全的;它僅用於提供建立池時使用的屬性。在建立單例的JedisPool 使用JedisPoolConfig須要注意線程安全問題,下面會有個demo介紹建立單例JedisPool
public class GenericObjectPoolConfig<T> extends BaseObjectPoolConfig<T> {

    /** * The default value for the {@code maxTotal} configuration attribute. * @see GenericObjectPool#getMaxTotal() */
    public static final int DEFAULT_MAX_TOTAL = 8;
    // ...

	// DEFAULT_MAX_TOTAL = 8
    private int maxTotal = DEFAULT_MAX_TOTAL;
	// DEFAULT_MAX_IDLE = 8
    private int maxIdle = DEFAULT_MAX_IDLE;
	// DEFAULT_MIN_IDLE = 0
    private int minIdle = DEFAULT_MIN_IDLE;
    // ...
}
複製代碼
  • JedisPoolConfig繼承了上面的優良基因,而後又對其餘的幾個設置屬性從新設值。

爲了方便使用,Jedis提供了JedisPoolConfig,它繼承了GenericObjectPoolConfig在空閒檢測上的一些設置。

public class JedisPoolConfig extends GenericObjectPoolConfig {
  public JedisPoolConfig() {
    // defaults to make your life with connection pool easier :)
    setTestWhileIdle(true);
    setMinEvictableIdleTimeMillis(60000);
    setTimeBetweenEvictionRunsMillis(30000);
    setNumTestsPerEvictionRun(-1);
  }
}
複製代碼

配置參數解析

JedisPoolConfig中能夠可以配置的參數有不少,鏈接池實現依賴apache 的commons-pool2。上面源碼也大體列舉了一些配置參數,下面在詳細說明一下。

把池理解爲工廠,池中的實例理解爲工人,以下圖,這樣池中的不少參數理解起來就比較容易了。

UTOOLS1571546063571.png

Jedis鏈接就是鏈接池中JedisPool管理的資源,JedisPool保證資源在一個可控範圍內,而且保障線程安全。使用合理的GenericObjectPoolConfig配置可以提高Redis的服務性能,下降資源開銷。下列兩表將對一些重要參數進行說明,並提供設置建議。

參數 說明 默認值 建議
maxTotal 資源池中的最大鏈接數 8 參見關鍵參數設置建議
maxIdle 資源池容許的最大空閒鏈接數 8 參見關鍵參數設置建議
minIdle 資源池確保的最少空閒鏈接數 0 參見關鍵參數設置建議
blockWhenExhausted 當資源池用盡後,調用者是否要等待。只有當值爲true時,下面的maxWaitMillis纔會生效。 true 建議使用默認值。
maxWaitMillis 當資源池鏈接用盡後,調用者的最大等待時間(單位爲毫秒)。 -1(表示永不超時) 不建議使用默認值。
testOnBorrow 向資源池借用鏈接時是否作鏈接有效性檢測(ping)。檢測到的無效鏈接將會被移除。 false 業務量很大時候建議設置爲false,減小一次ping的開銷。
testOnReturn 向資源池歸還鏈接時是否作鏈接有效性檢測(ping)。檢測到無效鏈接將會被移除。 false 業務量很大時候建議設置爲false,減小一次ping的開銷。
jmxEnabled 是否開啓JMX監控 true 建議開啓,請注意應用自己也須要開啓。

空閒Jedis對象檢測由下列四個參數組合完成,testWhileIdle是該功能的開關。

名稱 說明 默認值 建議
testWhileIdle 是否開啓空閒資源檢測。 false true
timeBetweenEvictionRunsMillis 空閒資源的檢測週期(單位爲毫秒) -1(不檢測) 建議設置,週期自行選擇,也能夠默認也可使用下方JedisPoolConfig 中的配置。
minEvictableIdleTimeMillis 資源池中資源的最小空閒時間(單位爲毫秒),達到此值後空閒資源將被移除。 180000(即30分鐘) 可根據自身業務決定,通常默認值便可,也能夠考慮使用下方JeidsPoolConfig中的配置。
numTestsPerEvictionRun 作空閒資源檢測時,每次檢測資源的個數。 3 可根據自身應用鏈接數進行微調,若是設置爲 -1,就是對全部鏈接作空閒監測。

說明 能夠在org.apache.commons.pool2.impl.BaseObjectPoolConfig中查看所有默認值。

關鍵參數設置建議

maxTotal(最大鏈接數)

想合理設置maxTotal(最大鏈接數)須要考慮的因素較多,如:

  • 業務但願的Redis併發量;
  • 客戶端執行命令時間;
  • Redis資源,例如nodes (如應用個數等) * maxTotal不能超過Redis的最大鏈接數;
  • 資源開銷,例如雖然但願控制空閒鏈接,但又不但願由於鏈接池中頻繁地釋放和建立鏈接形成沒必要要的開銷。

假設一次命令時間,即borrow|return resource加上Jedis執行命令 ( 含網絡耗時)的平均耗時約爲1ms,一個鏈接的QPS大約是1000,業務指望的QPS是50000,那麼理論上須要的資源池大小是50000 / 1000 = 50。

但事實上這只是個理論值,除此以外還要預留一些資源,因此maxTotal能夠比理論值大一些。這個值不是越大越好,一方面鏈接太多會佔用客戶端和服務端資源,另外一方面對於Redis這種高QPS的服務器,若是出現大命令的阻塞,即便設置再大的資源池也無濟於事。

maxIdle與minIdle

maxIdle實際上纔是業務須要的最大鏈接數,maxTotal 是爲了給出餘量,因此 maxIdle 不要設置得太小,不然會有new Jedis(新鏈接)開銷,而minIdle是爲了控制空閒資源檢測。

鏈接池的最佳性能是maxTotal=maxIdle,這樣就避免了鏈接池伸縮帶來的性能干擾。但若是併發量不大或者maxTotal設置太高,則會致使沒必要要的鏈接資源浪費。

您能夠根據實際總QPS和調用Redis的客戶端規模總體評估每一個節點所使用的鏈接池大小。

使用監控獲取合理值

在實際環境中,比較可靠的方法是經過監控來嘗試獲取參數的最佳值。能夠考慮經過JMX等方式實現監控,從而找到合理值。

上面參數配置:JedisPool資源池優化

建立JedisPool代碼

// volatile 修飾
private static volatile JedisPool jedisPool = null;

private JedisPoolUtils(){
}

public static JedisPool getJedisPoolInstance() {
    // 使用雙重檢查建立單例
    if(null == jedisPool) {
        synchronized (JedisPoolUtils.class) {
            if(null == jedisPool) {
                JedisPoolConfig poolConfig = new JedisPoolConfig();
                poolConfig.setMaxTotal(10);
                poolConfig.setMaxIdle(10);
                poolConfig.setMinIdle(2);
                poolConfig.setMaxWaitMillis(30*1000);
                poolConfig.setTestOnBorrow(true);
                poolConfig.setTestOnReturn(true);
                poolConfig.setTimeBetweenEvictionRunsMillis(10*1000);
                poolConfig.setMinEvictableIdleTimeMillis(30*1000);
                poolConfig.setNumTestsPerEvictionRun(-1);
                jedisPool = new JedisPool(poolConfig,"localhost",6379);
            }
        }
    }
    return jedisPool;
}
複製代碼

實例建立和釋放大體流程解析

UTOOLS1571556369669.png

根據流程進行源碼解析

建立過程

使用pool.getResource()進行Jedis實例的建立。

//org.apache.commons.pool2.impl.GenericObjectPool#borrowObject(long)
public T borrowObject(final long borrowMaxWaitMillis) throws Exception {

    final boolean blockWhenExhausted = getBlockWhenExhausted();

    PooledObject<T> p = null;
    boolean create;
    final long waitTime = System.currentTimeMillis();

    while (p == null) {
        create = false;
        // 從空閒隊列中獲取
        p = idleObjects.pollFirst();
        if (p == null) {
            // 建立實例
            p = create();
            if (p != null) {
                create = true;
            }
        }
        // 吃資源是否耗盡
        if (blockWhenExhausted) {
            if (p == null) {
                // 等待時間小於0
                if (borrowMaxWaitMillis < 0) {
                    p = idleObjects.takeFirst();
                } else {
                    p = idleObjects.pollFirst(borrowMaxWaitMillis,
                                              TimeUnit.MILLISECONDS);
                }
            }
            if (p == null) {
                throw new NoSuchElementException(
                    "Timeout waiting for idle object");
            }
        } else {
            if (p == null) {
                throw new NoSuchElementException("Pool exhausted");
            }
        }
        if (!p.allocate()) {
            p = null;
        }

        if (p != null) {
            try {
                // 從新初始化要由池返回的實例。
                factory.activateObject(p);
            } catch (final Exception e) {

            }
        }
    }
    updateStatsBorrow(p, System.currentTimeMillis() - waitTime);

    return p.getObject();
}    

複製代碼

釋放過程

從Jedis3.0版本後pool.returnResource()遭棄用,官方重寫了Jedis的close方法用以代替;官方建議應用redis.clients.jedis#Jedis的close方法進行資源回收,官方代碼以下:

@Override
  public void close() {
    if (dataSource != null) {
      JedisPoolAbstract pool = this.dataSource;
      this.dataSource = null;
      if (client.isBroken()) {
        pool.returnBrokenResource(this);
      } else {
        pool.returnResource(this);
      }
    } else {
      super.close();
    }
  }
複製代碼

這裏主要看:pool.returnResource(this);

//org.apache.commons.pool2.impl.GenericObjectPool#returnObject
public void returnObject(final T obj) {
    // 獲取要釋放的實例對象
    final PooledObject<T> p = allObjects.get(new IdentityWrapper<>(obj));

    if (p == null) {
        if (!isAbandonedConfig()) {
            throw new IllegalStateException(
                "Returned object not currently part of this pool");
        }
        return; // Object was abandoned and removed
    }
	// 將對象標記爲返回池的狀態。
    markReturningState(p);

    final long activeTime = p.getActiveTimeMillis();
	
    // 這裏就和上面配置的參數有關係,釋放的時候是否作鏈接有效性檢測(ping)
    if (getTestOnReturn() && !factory.validateObject(p)) {
        try {
            destroy(p);
        } catch (final Exception e) {
            swallowException(e);
        }
        try {
            ensureIdle(1, false);
        } catch (final Exception e) {
            swallowException(e);
        }
        updateStatsReturn(activeTime);
        return;
    }
	
	// 檢查空閒對象,若是最大空閒對象數小於當前idleObjects大小,則銷燬
    final int maxIdleSave = getMaxIdle();
    if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
        try {
            destroy(p);
        } catch (final Exception e) {
            swallowException(e);
        }
    } else {
        // 不然加入到空閒隊列中,空閒隊列是一個雙端隊列
        // getLifo 也和配置的參數有關,默認True
        if (getLifo()) {
            // last in first out,加到隊頭
            idleObjects.addFirst(p);
        } else {
            // first in first out ,加到隊尾
            idleObjects.addLast(p);
        }
    }
    updateStatsReturn(activeTime);
}
複製代碼

上面建立和釋放刪除了一些代碼,具體完整代碼都是在GenericObjectPool類中。

小結,後悔有期

看完本文,應該大體對JedisPoolConfig有了必定的瞭解,指定裏面的一些配置參數,而且可以基本的參數調優,以及實例資源的建立和釋放的過程。

若是感謝興趣的夥伴能夠下載Jedis的源碼進行閱讀和學習,掌握了JedisPoolConfig的配置,其餘池化框架的配置也是大同小異,觸類旁通! 江湖不遠,後會有期!

若是須要Reids相關的資源能夠掃碼下方二維碼,裏面有Redis的相關資源!

備註: 因爲本人能力有限,文中如有錯誤之處,歡迎指正。


謝謝你的閱讀,若是您以爲這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你天天開心愉快!


Java編程技術樂園:一個分享編程知識的公衆號。跟着老司機一塊兒學習乾貨技術知識,天天進步一點點,讓小的積累,帶來大的改變!

掃描關注,後臺回覆【祕籍】,獲取珍藏乾貨! 99.9%的夥伴都很喜歡

image.png | center| 747x519

©天天都在變得更好的阿飛雲
相關文章
相關標籤/搜索