從源碼角度看JedisPoolConfig參數配置


做一個積極的人

編碼、改bug、提升自己

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

@

你好,JedisPoolConfig

Java中使用Jedis作為連接Redis的工具。在使用Jedis的也可以配置JedisPool連接池,JedisPool配置參數大部分是由JedisPoolConfig的對應項來賦值的。本文簡單總結幾個常用的配置,然后通過源碼(版本jedis-3.1.0)的角度讓你理解配置這些參數的原理。

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

池化的基本思想

1、可以在初始化的時候創建一些對象,當有需要使用的時候不直接從池中獲取,提高響應速度;

2、使用過的對象不進行銷毀,保存起來,等下一次需要對象的時候,拿出來重復使用,減少頻繁創建對象所造成的開銷;

3、創建的對象統一保存,方面管理和維護。

池化好處總結

1、提高響應的速度

2、降低資源的消耗

3、方便管理和維護

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編程技術樂園:一個分享編程知識。跟着老司機一起學習干貨技術知識,每天進步一點點,讓小的積累,帶來大的改變!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM