全部產品
Search
文件中心

Tair (Redis® OSS-Compatible):JedisPool資源集區最佳化

更新時間:Jun 19, 2024

JedisPool是Jedis用戶端的串連池,合理地設定JedisPool資源集區參數能夠有效地提升Redis效能與資源使用率。本文將對JedisPool的使用和資源集區的參數進行詳細說明,並提供最佳化配置的建議。

使用方法

以Jedis 2.9.0為例,其Maven依賴如下:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    <scope>compile</scope>
</dependency>

Jedis使用Apache Commons-pool2對資源集區進行管理,在定義JedisPool時需注意其關鍵參數GenericObjectPoolConfig(資源集區)。該參數的使用樣本如下,其中的參數的說明請參見下文。

GenericObjectPoolConfig jedisPoolConfig = new GenericObjectPoolConfig();
jedisPoolConfig.setMaxTotal(...);
jedisPoolConfig.setMaxIdle(...);
jedisPoolConfig.setMinIdle(...);
jedisPoolConfig.setMaxWaitMillis(...);
...

JedisPool的初始化方法如下:

// redisHost為執行個體的IP, redisPort 為執行個體連接埠,redisPassword 為執行個體的密碼,timeout 既是連線逾時又是讀寫逾時。
JedisPool jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, timeout, redisPassword);
// 執行命令如下。
Jedis jedis = null;
try {
    jedis = jedisPool.getResource();
    // 具體的命令。
    jedis.executeCommand()
} catch (Exception e) {
    logger.error(e.getMessage(), e);
} finally {
    // 在JedisPool模式下,Jedis會被歸還給資源集區。
    if (jedis != null) 
        jedis.close();
}

參數說明

Jedis串連就是串連池中JedisPool管理的資源,JedisPool保證資源在一個可控範圍內,並且保障安全執行緒。使用合理的GenericObjectPoolConfig配置能夠提升Redis的服務效能,降低資源開銷。下列兩表將對一些重要參數進行說明,並提供設定建議。

表 1. 資源設定與使用相關參數

參數

說明

預設值

建議

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對象檢測由下列四個參數組合完成。

表 2. 空閑資源檢測相關參數

名稱

說明

預設值

建議

testWhileIdle

是否在空閑資源監測時通過ping命令監測串連有效性,無效串連將被銷毀。

false

true

timeBetweenEvictionRunsMillis

空閑資源的檢測周期(單位為毫秒)

-1(不檢測)

建議設定,周期自行選擇,也可以預設也可以使用下方JedisPoolConfig 中的配置。

minEvictableIdleTimeMillis

資源集區中資源的最小空閑時間(單位為毫秒),達到此值後空閑資源將被移除。

1,800,000(即30分鐘)

可根據自身業務決定,一般預設值即可,也可以考慮使用下方JeidsPoolConfig中的配置。

numTestsPerEvictionRun

做空閑資源檢測時,每次檢測資源的個數。

3

可根據自身應用串連數進行微調,如果設定為 -1,就是對所有串連做空閑監測。

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

public class JedisPoolConfig extends GenericObjectPoolConfig {
  public JedisPoolConfig() {
    setTestWhileIdle(true);
    setMinEvictableIdleTimeMillis(60000);
    setTimeBetweenEvictionRunsMillis(30000);
    setNumTestsPerEvictionRun(-1);
    }
}
說明

可以在org.apache.commons.pool2.impl.BaseObjectPoolConfig中查看全部預設值。

關鍵參數設定建議

maxTotal(最大串連數)

想合理設定maxTotal(最大串連數)需要考慮的因素較多,如:

  • 業務希望的Redis並發量;

  • 用戶端執行命令時間;

  • Redis資源,例如nodes (如應用ECS個數等) * maxTotal不能超過Redis的最大串連數(可在執行個體詳情頁面查看);

  • 資源開銷,例如雖然希望控制空閑串連,但又不希望因為串連池中頻繁地釋放和建立串連造成不必要的開銷。

假設一次命令時間,即borrow|return resource加上Jedis執行命令 ( 含網路耗時)的平均耗時約為1ms,一個串連的QPS大約是1s/1ms = 1000,而業務期望的單個Redis的QPS是50000(業務總的QPS/Redis分區個數),那麼理論上需要的資源集區大小(即MaxTotal)是50000 / 1000 = 50。

但事實上這隻是個理論值,除此之外還要預留一些資源,所以maxTotal可以比理論值大一些。這個值不是越大越好,一方面串連太多會佔用用戶端和服務端資源,另一方面對於Redis這種高QPS的伺服器,如果出現大命令的阻塞,即使設定再大的資源集區也無濟於事。

maxIdle與minIdle

maxIdle實際上才是業務需要的最大串連數,maxTotal 是為了給出餘量,所以 maxIdle 不要設定得過小,否則會有new Jedis(新串連)開銷,而minIdle是為了控制空閑資源檢測。

串連池的最佳效能是maxTotal=maxIdle,這樣就避免了串連池伸縮帶來的效能幹擾。如果您的業務存在突峰訪問,建議設定這兩個參數的值相等;如果並發量不大或者maxIdle設定過高,則會導致不必要的串連資源浪費。

您可以根據實際總QPS和調用Redis的用戶端規模整體評估每個節點所使用的串連池大小。

使用監控擷取合理值

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

常見問題

資源不足

下面兩種情況均屬於無法從資源集區擷取到資源。

  • 逾時:

    redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
    …
    Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
  • blockWhenExhausted 為false,因此不會等待資源釋放:

    redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
    …
    Caused by: java.util.NoSuchElementException: Pool exhausted
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)

此類異常的原因不一定是資源集區不夠大,請參見關鍵參數設定建議中的分析。建議從網路、資源集區參數設定、資源集區監控(如果對JMX監控)、代碼(例如沒執行jedis.close())、慢查詢、DNS等方面進行排查。

預熱JedisPool

由於一些原因(如逾時時間設定較小等),專案在啟動成功後可能會出現逾時。JedisPool定義最大資源數、最小空閑資源數時,不會在串連池中建立Jedis串連。初次使用時,池中沒有資源使用則會先建立一個new Jedis,使用後再放入資源集區,該過程會有一定的時間開銷,所以建議在定義JedisPool後,以最小空閑數量為基準對JedisPool進行預熱,樣本如下:

List<Jedis> minIdleJedisList = new ArrayList<Jedis>(jedisPoolConfig.getMinIdle());

for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
    Jedis jedis = null;
    try {
        jedis = pool.getResource();
        minIdleJedisList.add(jedis);
        jedis.ping();
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    } finally {
    }
}

for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
    Jedis jedis = null;
    try {
        jedis = minIdleJedisList.get(i);
        jedis.close();
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    } finally {
    
    }
}

如有其它報錯,請參見常見報錯