使用JedisPool資源池操做Redis,並進行性能優化

1、使用方法

-----------------------------------------
private volatile static JedisPool pool = null; //本地測試
private volatile static JedisSentinelPool sentinelPool = null;
private static GenericObjectPoolConfig config = null;
private static final int TIMEOUT = 10000; // 10秒

static {
config = new JedisPoolConfig();
config.setMaxTotal(500);
// 控制一個pool最多有多少個狀態爲idle(空閒的)的jedis實例。
config.setMaxIdle(5);
// 表示當borrow(引入)一個jedis實例時,最大的等待時間,若是超過等待時間,則直接拋出JedisConnectionException;
config.setMaxWaitMillis(1000 * 60 * 1000);
// 在borrow一個jedis實例時,是否提早進行validate操做;若是爲true,則獲得的jedis實例均是可用的;
config.setTestOnBorrow(false);
}

public static void init(String host, int port, String password) {
if(pool == null) {
synchronized(RedisUtils.class) {
if(pool == null) {
pool = new JedisPool(config, checkNotNull(host), checkNotNull(port), TIMEOUT,checkNotNull(password));
}
}
}
}
-----------------------------------------
-----------------------------------------
//初始化調用
RedisUtils.init(host, port, password);
-----------------------------------------
-----------------------------------------

//得到 redis 資源 與 釋放 redis 資源
/**
* 獲取jedis資源
*
* @return
*/
public static Jedis getJedis() {
Jedis jedis = null;
try {
if(pool != null) {
jedis = pool.getResource();
} else {
jedis = sentinelPool.getResource();
}
} catch (Throwable t) {
logger.error("", t);
}
return jedis;
}

/**
* 歸還jedis資源
*
* @param jedis
*/
@SuppressWarnings("deprecation")
public static void returnResource(Jedis jedis) {
try {
if(pool != null) {
pool.returnResource(jedis);
} else {
sentinelPool.returnResource(jedis);
}
} catch (Throwable t) {
logger.error("", t);
if(pool != null) {
pool.returnBrokenResource(jedis);
} else {
sentinelPool.returnBrokenResource(jedis);
}
}
}
-----------------------------------------
-----------------------------------------
數據操做,可使用 Pipeline 操做:
Pipeline pipeline = jedis.pipelined();
Response<Long> response = pipeline.sadd(key, member);
pipeline.expireAt(key, expireTime);
-----------------------------------------


2、參數說明

JedisPool保證資源在一個可控範圍內,而且提供了線程安全,可是一個合理的GenericObjectPoolConfig配置能爲應用使用Redis保駕護航,下面將對它的一些重要參數進行說明和建議:java

在當前環境下,Jedis鏈接就是資源,JedisPool管理的就是Jedis鏈接。node

1. 資源設置和使用

序號 參數名 含義 默認值 使用建議
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 建議開啓,但應用自己也要開啓

2.空閒資源監測

空閒Jedis對象檢測,下面四個參數組合來完成,testWhileIdle是該功能的開關。redis

序號 參數名 含義 默認值 使用建議
1 testWhileIdle 是否開啓空閒資源監測 false true
2 timeBetweenEvictionRunsMillis 空閒資源的檢測週期(單位爲毫秒) -1:不檢測 建議設置,週期自行選擇,也能夠默認也可使用下面JedisPoolConfig中的配置
3 minEvictableIdleTimeMillis 資源池中資源最小空閒時間(單位爲毫秒),達到此值後空閒資源將被移除 1000 60 30 = 30分鐘 可根據自身業務決定,大部分默認值便可,也能夠考慮使用下面JeidsPoolConfig中的配置
4 numTestsPerEvictionRun 作空閒資源檢測時,每次的採樣數 3 可根據自身應用鏈接數進行微調,若是設置爲-1,就是對全部鏈接作空閒監測

爲了方便使用,Jedis提供了JedisPoolConfig,它自己繼承了GenericObjectPoolConfig設置了一些空閒監測設置apache

複製代碼
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中看到。數組

3、資源池大小(maxTotal)、空閒(maxIdle minIdle)設置建議

1.maxTotal:最大鏈接數

實際上這個是一個很難回答的問題,考慮的因素比較多:安全

  • 業務但願Redis併發量
  • 客戶端執行命令時間
  • Redis資源:例如 nodes(例如應用個數) * maxTotal 是不能超過redis的最大鏈接數。
  • 資源開銷:例如雖然但願控制空閒鏈接,可是不但願由於鏈接池的頻繁釋放建立鏈接形成沒必要靠開銷。

以一個例子說明,假設:服務器

  • 一次命令時間(borrow|return resource + Jedis執行命令(含網絡) )的平均耗時約爲1ms,一個鏈接的QPS大約是1000
  • 業務指望的QPS是50000

那麼理論上須要的資源池大小是50000 / 1000 = 50個。但事實上這是個理論值,還要考慮到要比理論值預留一些資源,一般來說maxTotal能夠比理論值大一些。網絡

但這個值不是越大越好,一方面鏈接太多佔用客戶端和服務端資源,另外一方面對於Redis這種高QPS的服務器,一個大命令的阻塞即便設置再大資源池仍然會無濟於事。併發

2. maxIdle minIdle

maxIdle實際上纔是業務須要的最大鏈接數,maxTotal是爲了給出餘量,因此maxIdle不要設置太小,不然會有new Jedis(新鏈接)開銷,而minIdle是爲了控制空閒資源監測。性能

鏈接池的最佳性能是maxTotal = maxIdle ,這樣就避免鏈接池伸縮帶來的性能干擾。可是若是併發量不大或者maxTotal設置太高,會致使沒必要要的鏈接資源浪費。
能夠根據實際總OPS和調用redis客戶端的規模總體評估每一個節點所使用的鏈接池。

3.監控

實際上最靠譜的值是經過監控來獲得「最佳值」的,能夠考慮經過一些手段(例如jmx)實現監控,找到合理值。

4、常見問題

1.資源「不足"

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

2. 預熱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 {
    
    }
}
複製代碼
相關文章
相關標籤/搜索