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的服務效能,降低資源開銷。下列兩表將對一些重要參數進行說明,並提供設定建議。
參數 | 說明 | 預設值 | 建議 |
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 | 是否在空閑資源監測時通過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 {
}
}
如有其它報錯,請參見常見報錯。