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 {
}
}
如有其它報錯,請參見常見報錯。