背景:
最近使用jedis(redis)開發一項功能,查閱日誌發現,服務運行一段時間之後,就會出現
redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool 的異常,
重啓就好了,但是過一段時間又出現這種情況;
我這裏問題比較簡單,釋放連接的方法忘記調用了,應該是沒有及時釋放連接造成的連接池內無連接可用。
但是關於這個問題,發現網上有種種說法,主要是連接池配置和釋放連接,關於這個問題,我們不妨深入思考下:
一、連接池配置參數怎麼設置?這些參數跟什麼有關係?
1、最大連接數:MAX_ACTIVE,指的是支持同時在使用的最大的連接數量:
參數的設置並不是越大越好,而是要看自己的實際使用情況,也就是需要的併發數是多少?
比如,系統併發量一般100,但是你的設置是50,顯然這個時候加大設置是很有用的。
但是,又比如這個業務最大可能併發100個鏈接,那麼就沒必要設置成2000,設置太大也可能會浪費系統性能;
如果這時候設置遠遠高於實際了,還是出現連接不夠用的情況,那麼基本上就是你使用後忘記釋放了。
2、等待可用連接的最大時間:MAX_WAIT,這個時間設置,要結合最大連接數、實際併發數、還有系統性能一起考慮,一般不宜過大,否則會導致請求響應時間過長,甚至請求失敗。但是也不能過小,如果系統資源比較富裕沒什麼影響,但是相反的話,過小的情況也可能造成較多的請求失敗
比如,設置成1分鐘,那麼假設此時併發數較高(MAX_ACTIVE一直佔用着),一直沒有得到可用連接,那麼這個請求就會出現一直等待的情況,可能會出現給客戶的感覺上系統反應卡頓的不良體驗。
3、有時爲了保證請求快速得到響應,保持一定的空閒連接(setMinIdle)。在連接池飽和狀態,最多有(MAX_ACTIVE-MinIdle)個連接數;
4、如果併發數很大呢?大到超過redis支持的最大連接數?
在 Redis2.4 中,最大連接數是被直接硬編碼在代碼裏面的,而在2.6版本中這個值變成可配置的。maxclients 的默認值是 10000.也就是說,redis默認最多允許10000個連接。當然這還要看硬件環境,CPU/內存情況;
真的有再大的需求,就只能是分佈式/集羣了,這裏暫時不討論。
附上jedis工具類中的獲取連接方法:
private static String ADDR = "****";
private static int PORT = 6379;
//訪問密碼
private static String AUTH = "****";
//可用連接實例的最大數目,默認值爲8;
//如果賦值爲-1,則表示不限制;如果pool已經分配了maxActive個jedis實例,則此時pool的狀態爲exhausted(耗盡)。
private static int MAX_ACTIVE = 100;
//控制一個pool最多有多少個狀態爲idle(空閒的)的jedis實例,默認值也是8。
private static int MAX_IDLE = 10;
//等待可用連接的最大時間,單位毫秒,默認值爲-1,表示永不超時。如果超過等待時間,則直接拋出JedisConnectionException;
private static int MAX_WAIT = 1000;
private static int TIMEOUT = 10000;
//在borrow一個jedis實例時,是否提前進行validate操作;如果爲true,則得到的jedis實例均是可用的;
private static boolean TEST_ON_BORROW = true;
private static JedisPool jedisPool = null;
//獲取鏈接
public static synchronized Jedis getJedis(){
if(jedisPool==null){
log.info("jedisPool==null , to reGet ");
try {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//指定連接池中最大空閒連接數
jedisPoolConfig.setMaxIdle(MAX_IDLE);
//鏈接池中創建的最大連接數
jedisPoolConfig.setMaxTotal(MAX_ACTIVE);
//設置創建鏈接的超時時間
jedisPoolConfig.setMaxWaitMillis(MAX_WAIT);
//表示連接池在創建鏈接的時候會先測試一下鏈接是否可用,這樣可以保證連接池中的鏈接都可用的。
jedisPoolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(jedisPoolConfig,ADDR, PORT, TIMEOUT, AUTH);
} catch (Exception e) {
log.error("jedisPool get error "+e.toString());
}
}
return jedisPool.getResource();
}
二、釋放連接的替代方法
在網上的資料,很多都是已經廢棄不用的方法,我這把替代方法貼一下:
starting from Jedis 3.0 this method will not be exposed. Resource cleanup should be done using @see {@link redis.clients.jedis.Jedis#close()} (廢棄方法returnResource和returnBrokenResource的註釋)
//回收鏈接
public static synchronized void returnResource(Jedis jedis){
try {
if(jedis!=null){
/*
* 這些方法已經廢棄
*/
jedisPool.returnResource(jedis);
// jedisPool.returnBrokenResource(jedis);
log.info("returnResource success ");
}
} catch (Exception e) {
log.error("returnResource error "+e.toString());
}
}
//釋放鏈接
public static synchronized void returnToPool(Jedis jedis){
try {
if(jedis!=null){
jedis.close();//替代方法
log.info("returnToPool success ");
}
} catch (Exception e) {
log.error("returnToPool error "+e.toString());
}
}
這次就總結到這兒,下次需要更深入研究下redis分佈式緩存。