一、介紹
1、Jedis開源庫提供了一個負責管理Jedis連接對象的池,名爲JedisPool類,位於redis.clients.jedis包中。爲了防止數據庫連接的頻繁創建、銷燬帶來的性能損耗。
二、JedisPool的配置
JedisPoolConfig配置類,位於redis.clients.jedis包中。這個連接池的配置類負責配置JedisPool的參數。JedisPoolConfig配置類涉及到很多與連接管理和使用有關的參數。
1、maxTotal:資源池中最大的連接數 默認:8
2、maxIdle:資源池允許最大空閒的連接數 默認:8
3、minIdle:資源池確保最少空閒的連接數 默認0
開啓JedisPool空閒連接的有效性檢測,如果空閒連接無效,就銷燬。銷燬連接後,連接數量就少了,如果小於minIdle數量,就新建連接,維護數量不少於minIdle的數量。minIdle確保了線程池中有最小的空閒Jedis實例的數量。
4、blockWhenExhausted:當資源池用盡後,調用者是否要等待,默認值爲true。當爲true時,maxWaitMillis纔會生效。
5、maxWaitMillis:當資源池連接用盡後,調用者的最大等待時間(單位爲毫秒)。默認值爲-1,表示永不超時。
6、testOnBorrow:向資源池借用連接時,是否做有效性檢測(ping命令),如果是無效連接,會被移除,默認值爲false,表示不做檢測。
- 如果爲true,則得到的Jedis實例均是可用的。
- 在業務量小的應用場景,建議設置爲true,確保連接可用;
- 在業務量很大的應用場景,建議設置爲false(默認值),少一次ping命令的開銷,有助於提升性能。
7、testOnReturn:向資源池歸還連接時,是否做有效性檢測(ping命令),如果是無效連接,會被移除,默認值爲false,表示不做檢測。
8、testWhileIdle:如果爲true,表示用一個專門的線程對空閒的連接進行有效性的檢測掃描,如果有效性檢測失敗,即表示無效連接,會從資源池中移除。
選項存在一個附加條件,需要配置項timeBetweenEvictionRunsMillis的值大於0;否則,testWhileIdle不會生效。
9、timeBetweenEvictionRunsMillis:表示兩次空閒連接掃描的活動之間,要睡眠的毫秒數,默認爲30000毫秒,也就是30秒鐘。
10、minEvictableIdleTimeMillis:表示一個Jedis連接至少停留在空閒狀態的最短時間,然後才能被空閒連接掃描線程進行有效性檢測,默認值爲60000毫秒,即60秒。
- 一條Jedis連接只有在空閒60秒後,纔會參與空閒線程的有效性檢測。
- 選項存在一個附加條件,需要在timeBetweenEvictionRunsMillis大於0時纔會生效。也就是說,如果不啓動空閒檢測線程,這個參數也沒有什麼意義。
11、numTestsPerEvictionRun:表示空閒檢測線程每次最多掃描的Jedis連接數,默認值爲-1,表示掃描全部的空閒連接。
12、jmxEnabled:是否開啓jmx監控,默認值爲true。
四、JedisPool創建和預熱
1、創建JedisPool連接池,創建一個JedisPoolConfig配置實例
- 配置
package com.example.actuatordemo.redis.conf;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
/**
* @author haoxiansheng
*/
public class JedisPoolConfig extends GenericObjectPoolConfig {
/**
* 有個實際的問題:如何推算一個連接池的最大連接數maxTotal呢?
* 實際上,這是一個很難精準回答的問題,主要是依賴的因素比較多。
* 大致的推算方法是:業務QPS/單連接的QPS = 最大連接數。如何推算單個Jedis連接的QPS呢?
* 假設一個Jedis命令操作的時間約爲5ms(包含borrow +return +Jedis執行命令 + 網絡延遲),那麼,單個Jedis連接的QPS大約是100/5 =200。
* 如果業務期望的QPS是100000,則需要的最大連接數爲100000/200 =500。
* <p>
* 在實際的生產場景中,還要預留一些資源,通常來講所配置的maxTotal要比理論值大一些。
* 如果連接數確實太多,可以考慮Redis集羣,那麼單個Redis節點的最大連接數的公式爲:
* maxTotal= 預估的連接數 / nodes節點數。
* <p>
* 在併發量不大時,maxTotal設置過高會導致不必要的連接資源的浪費。可以根據實際總QPS和nodes節點數,合理評估每個節點所使用的最大連接數。
* <p>
* <p>
* 再看一個問題:如何推算連接池的最大空閒連接數maxIdle值呢?實際上,maxTotal只是給出了一個連接數量的上限,maxIdle實際上纔是業務可用的最大連接數,
* 從這個層面來說,maxIdle不能設置過小,否則會有創建、銷燬連接的開銷。
* 使得連接池達到最佳性能的設置是maxTotal = maxIdle,應儘可能地避免由於頻繁地創建和銷燬Jedis連接所帶來的連接池性能的下降。
*/
public JedisPoolConfig() {
this.setTestWhileIdle(true);
this.setMinEvictableIdleTimeMillis(60000L);
this.setTimeBetweenEvictionRunsMillis(30000L);
this.setNumTestsPerEvictionRun(-1);
}
}
- 創建pool
package com.example.actuatordemo.redis.conf;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ArrayList;
import java.util.List;
/**
* @author haoxiansheng
* 創建JedisPool連接池的一般步驟爲創建一個JedisPoolConfig配置實例;
* JedisPoolConfig、Redis IP、Redis端口和其他可選選項(如超時時間、Auth密碼)爲參數,構造一個JedisPool連接池。
*/
@Slf4j
public class JredisPoolBuilder {
public static final int MAX_IDLE = 50;
public static final int MAX_TOTAL = 50;
private static JedisPool pool = null;
// 創建連接池
private static JedisPool buildPool() {
if (pool == null) {
long start = System.currentTimeMillis();
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(MAX_IDLE);
config.setMaxWaitMillis(1000 * 10);
// 在brrow 一個jedis 實例 是否提前進行有效檢測操作;
// 如果爲true,則得到的jedis 實例均是可用的
config.setTestOnBorrow(true);
// new JedisPool(config, ADDR ,PORT, TIMEOUT);
pool = new JedisPool(config, "127.0.0.1", 6379, 10000);
long end = System.currentTimeMillis();
log.info("buildPool毫秒數=>{}", (end - start));
}
return pool;
}
// 連接池的預熱
private static void hotPool() {
long start = System.currentTimeMillis();
List<Jedis> minIdleJedisList = new ArrayList<>(MAX_IDLE);
Jedis jedis = null;
for (int i = 0; i < MAX_IDLE; i++) {
try {
jedis = pool.getResource();
minIdleJedisList.add(jedis);
jedis.ping();
} catch (Exception e) {
log.info("e=>{}", e.getMessage());
} finally {
}
for (int j = 0; j < MAX_IDLE; j++) {
try {
jedis = minIdleJedisList.get(i);
jedis.close();
;
} catch (Exception e) {
log.error("e=>{}", e.getMessage());
} finally {
}
}
long end = System.currentTimeMillis();
log.info("hotPool毫秒數=>{}", (end - start));
}
}
static {
// 創建連接池
buildPool();
// 預熱連接池
hotPool();
}
public static Jedis getJedis() {
return pool.getResource();
}
}
- 使用
package com.example.actuatordemo.redis.conf;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
/**
* @author haoxiansheng
*/
@Slf4j
public class JredisPoolTest {
public static final int NUM = 200;
public static final String ZEST_KEY = "zset1";
// 測試刪除
public static void main(String[] args) {
}
public static void testSet() {
testDel();
try (Jedis redis = JredisPoolBuilder.getJedis()) {
int loop = 0;
long start = System.currentTimeMillis();
while (loop < NUM) {
redis.zadd(ZEST_KEY, loop, "field-" + loop);
loop++;
}
long end = System.currentTimeMillis();
log.info("設置Zset=>{}次,毫秒數=>{}", loop, (end - start));
}
}
public static void testDel() {
Jedis redis = null;
try {
redis = JredisPoolBuilder.getJedis();
long start = System.currentTimeMillis();
redis.del(ZEST_KEY);
long end = System.currentTimeMillis();
log.info("刪除zset1 毫秒數=>{}", (end - start));
} finally {
// 關閉 連接池
if (redis != null) {
redis.close();
}
}
}
}