摘要: 合理的JedisPool資源池參數設置能爲業務使用Redis保駕護航,本文將對JedisPool的使用、資源池的參數進行詳細說明,最後給出「最合理」配置。php
合理的JedisPool資源池參數設置能爲業務使用Redis保駕護航,本文將對JedisPool的使用、資源池的參數進行詳細說明,最後給出「最合理」配置。css
以官方的2.9.0爲例子(Jedis Release),Maven依賴以下:java
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> <scope>compile</scope> </dependency>
Jedis使用apache commons-pool2對Jedis資源池進行管理,因此在定義JedisPool時一個很重要的參數就是資源池GenericObjectPoolConfig,使用方式以下,其中有不少資源管理和使用的參數(具體看第二節)node
注意:後面會提到建議用JedisPoolConfig代替GenericObjectPoolConfiggit
GenericObjectPoolConfig jedisPoolConfig = new GenericObjectPoolConfig();
jedisPoolConfig.setMaxTotal(..);
jedisPoolConfig.setMaxIdle(..);
jedisPoolConfig.setMinIdle(..); jedisPoolConfig.setMaxWaitMillis(..); ...
JedisPool的初始化以下:github
// redisHost和redisPort是實例的IP和端口 // redisPassword是實例的密碼 // timeout,這裏既是鏈接超時又是讀寫超時,從Jedis 2.8開始有區分connectionTimeout和soTimeout的構造函數 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(); }
JedisPool保證資源在一個可控範圍內,而且提供了線程安全,可是一個合理的GenericObjectPoolConfig配置能爲應用使用Redis保駕護航,下面將對它的一些重要參數進行說明和建議:redis
在當前環境下,Jedis鏈接就是資源,JedisPool管理的就是Jedis鏈接。apache
序號 | 參數名 | 含義 | 默認值 | 使用建議 |
---|---|---|---|---|
1 | maxTotal | 資源池中最大鏈接數 | 8 | 設置建議見下節 |
2 | maxIdle | 資源池容許最大空閒的鏈接數 | 8 | 設置建議見下節 |
3 | minIdle | 資源池確保最少空閒的鏈接數 | 0 | 設置建議見下節 |
4 | blockWhenExhausted | 當資源池用盡後,調用者是否要等待。只有當爲true時,下面的maxWaitMillis纔會生效 | true | 建議使用默認值 |
5 | maxWaitMillis | 當資源池鏈接用盡後,調用者的最大等待時間(單位爲毫秒) | -1:表示永不超時 | 不建議使用默認值 |
6 | testOnBorrow | 向資源池借用鏈接時是否作鏈接有效性檢測(ping),無效鏈接會被移除 | false | 業務量很大時候建議設置爲false(多一次ping的開銷)。 |
7 | testOnReturn | 向資源池歸還鏈接時是否作鏈接有效性檢測(ping),無效鏈接會被移除 | false | 業務量很大時候建議設置爲false(多一次ping的開銷)。 |
8 | jmxEnabled | 是否開啓jmx監控,可用於監控 | true | 建議開啓,但應用自己也要開啓 |
空閒Jedis對象檢測,下面四個參數組合來完成,testWhileIdle是該功能的開關。數組
序號 | 參數名 | 含義 | 默認值 | 使用建議 |
---|---|---|---|---|
1 | testWhileIdle | 是否開啓空閒資源監測 | false | true |
2 | timeBetweenEvictionRunsMillis | 空閒資源的檢測週期(單位爲毫秒) | -1:不檢測 | 建議設置,週期自行選擇,也能夠默認也可使用下面JedisPoolConfig中的配置 |
3 | minEvictableIdleTimeMillis | 資源池中資源最小空閒時間(單位爲毫秒),達到此值後空閒資源將被移除 | 1000 60 30 = 30分鐘 | 可根據自身業務決定,大部分默認值便可,也能夠考慮使用下面JeidsPoolConfig中的配置 |
4 | numTestsPerEvictionRun | 作空閒資源檢測時,每次的採樣數 | 3 | 可根據自身應用鏈接數進行微調,若是設置爲-1,就是對全部鏈接作空閒監測 |
爲了方便使用,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); } }
全部默認值能夠從org.apache.commons.pool2.impl.BaseObjectPoolConfig中看到。
實際上這個是一個很難回答的問題,考慮的因素比較多:
以一個例子說明,假設:
那麼理論上須要的資源池大小是50000 / 1000 = 50個。但事實上這是個理論值,還要考慮到要比理論值預留一些資源,一般來說maxTotal能夠比理論值大一些。
但這個值不是越大越好,一方面鏈接太多佔用客戶端和服務端資源,另外一方面對於Redis這種高QPS的服務器,一個大命令的阻塞即便設置再大資源池仍然會無濟於事。
maxIdle實際上纔是業務須要的最大鏈接數,maxTotal是爲了給出餘量,因此maxIdle不要設置太小,不然會有new Jedis(新鏈接)開銷,而minIdle是爲了控制空閒資源監測。
鏈接池的最佳性能是maxTotal = maxIdle ,這樣就避免鏈接池伸縮帶來的性能干擾。可是若是併發量不大或者maxTotal設置太高,會致使沒必要要的鏈接資源浪費。
能夠根據實際總OPS和調用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)
或者
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)
兩種狀況均屬於沒法從資源池獲取到資源,但第一種是超時,第二種是由於blockWhenExhausted爲false根本就不等。
遇到此類異常,不要盲目的認爲資源池不夠大,第三節已經進行了分析。具體緣由能夠排查:網絡、資源池參數設置、資源池監控(若是對jmx監控)、代碼(例如沒執行jedis.close())、慢查詢、DNS等問題。
具體能夠參考該文章:https://www.atatech.org/articles/77799
因爲一些緣由(例如超時時間設置較小緣由),有的項目在啓動成功後會出現超時。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 { } }