做一個積極的人
編碼、改bug、提升自己
我有一個樂園,面向編程,春暖花開!
@
你好,JedisPoolConfig
Java中使用Jedis
作為連接Redis
的工具。在使用Jedis
的也可以配置JedisPool
連接池,JedisPool
配置參數大部分是由JedisPoolConfig
的對應項來賦值的。本文簡單總結幾個常用的配置,然后通過源碼(版本jedis-3.1.0
)的角度讓你理解配置這些參數的原理。
首先了解一下池化((對象池、數據庫連接池、線程池等等))的一些思想和好處。方便后面對JedisPoolConfig
的配置的理解。
池化的基本思想:
1、可以在初始化的時候創建一些對象,當有需要使用的時候不直接從池中獲取,提高響應速度;
2、使用過的對象不進行銷毀,保存起來,等下一次需要對象的時候,拿出來重復使用,減少頻繁創建對象所造成的開銷;
3、創建的對象統一保存,方面管理和維護。
池化好處總結:
1、提高響應的速度
2、降低資源的消耗
3、方便管理和維護
JedisPoolConfig
配置說明
類圖和源碼解析
首先看一下類圖:
BaseGenericObjectPool
:封裝公共的配置的參數。
private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
// DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L
private long minEvictableIdleTimeMillis =
DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
// DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L
private long timeBetweenEvictionRunsMillis =
DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
// DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3
private int numTestsPerEvictionRun =
DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
// DEFAULT_TEST_ON_CREATE = false
private boolean testOnCreate = DEFAULT_TEST_ON_CREATE;
// DEFAULT_TEST_ON_BORROW = false
private boolean testOnBorrow = DEFAULT_TEST_ON_BORROW;
// DEFAULT_TEST_ON_RETURN = false
private boolean testOnReturn = DEFAULT_TEST_ON_RETURN;
// DEFAULT_TEST_WHILE_IDLE = false
private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE;
//...
}
GenericObjectPoolConfig
:繼承BaseGenericObjectPool
,內部代碼很簡單,封裝了GenericObjectPool
的配置。主要是maxTotal
、maxIdle
、minIdle
。
此類不是線程安全的;它僅用於提供創建池時使用的屬性。在創建單例的JedisPool
使用JedisPoolConfig
需要注意線程安全問題,下面會有個demo介紹創建單例JedisPool
。
public class GenericObjectPoolConfig<T> extends BaseObjectPoolConfig<T> {
/**
* The default value for the {@code maxTotal} configuration attribute.
* @see GenericObjectPool#getMaxTotal()
*/
public static final int DEFAULT_MAX_TOTAL = 8;
// ...
// DEFAULT_MAX_TOTAL = 8
private int maxTotal = DEFAULT_MAX_TOTAL;
// DEFAULT_MAX_IDLE = 8
private int maxIdle = DEFAULT_MAX_IDLE;
// DEFAULT_MIN_IDLE = 0
private int minIdle = DEFAULT_MIN_IDLE;
// ...
}
JedisPoolConfig
繼承了上面的優良基因,然后又對其他的幾個設置屬性重新設值。
為了方便使用,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);
}
}
配置參數解析
JedisPoolConfig
中可以能夠配置的參數有很多,連接池實現依賴apache 的commons-pool2
。上面源碼也大致列舉了一些配置參數,下面在詳細說明一下。
把池理解為工廠,池中的實例理解為工人,如下圖,這樣池中的很多參數理解起來就比較容易了。
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是該功能的開關。
名稱 | 說明 | 默認值 | 建議 |
---|---|---|---|
testWhileIdle | 是否開啟空閑資源檢測。 | false | true |
timeBetweenEvictionRunsMillis | 空閑資源的檢測周期(單位為毫秒) | -1(不檢測) | 建議設置,周期自行選擇,也可以默認也可以使用下方JedisPoolConfig 中的配置。 |
minEvictableIdleTimeMillis | 資源池中資源的最小空閑時間(單位為毫秒),達到此值后空閑資源將被移除。 | 180000(即30分鍾) | 可根據自身業務決定,一般默認值即可,也可以考慮使用下方JeidsPoolConfig中的配置。 |
numTestsPerEvictionRun | 做空閑資源檢測時,每次檢測資源的個數。 | 3 | 可根據自身應用連接數進行微調,如果設置為 -1,就是對所有連接做空閑監測。 |
說明 可以在org.apache.commons.pool2.impl.BaseObjectPoolConfig中查看全部默認值。
關鍵參數設置建議
maxTotal(最大連接數)
想合理設置maxTotal(最大連接數)需要考慮的因素較多,如:
- 業務希望的Redis並發量;
- 客戶端執行命令時間;
- Redis資源,例如nodes (如應用個數等) * maxTotal不能超過Redis的最大連接數;
- 資源開銷,例如雖然希望控制空閑連接,但又不希望因為連接池中頻繁地釋放和創建連接造成不必要的開銷。
假設一次命令時間,即borrow|return resource加上Jedis執行命令 ( 含網絡耗時)的平均耗時約為1ms,一個連接的QPS大約是1000,業務期望的QPS是50000,那么理論上需要的資源池大小是50000 / 1000 = 50。
但事實上這只是個理論值,除此之外還要預留一些資源,所以maxTotal可以比理論值大一些。這個值不是越大越好,一方面連接太多會占用客戶端和服務端資源,另一方面對於Redis這種高QPS的服務器,如果出現大命令的阻塞,即使設置再大的資源池也無濟於事。
maxIdle與minIdle
maxIdle實際上才是業務需要的最大連接數,maxTotal 是為了給出余量,所以 maxIdle 不要設置得過小,否則會有new Jedis
(新連接)開銷,而minIdle是為了控制空閑資源檢測。
連接池的最佳性能是maxTotal=maxIdle,這樣就避免了連接池伸縮帶來的性能干擾。但如果並發量不大或者maxTotal設置過高,則會導致不必要的連接資源浪費。
您可以根據實際總QPS和調用Redis的客戶端規模整體評估每個節點所使用的連接池大小。
使用監控獲取合理值
在實際環境中,比較可靠的方法是通過監控來嘗試獲取參數的最佳值。可以考慮通過JMX等方式實現監控,從而找到合理值。
上面參數配置:JedisPool資源池優化
創建JedisPool
代碼
// volatile 修飾
private static volatile JedisPool jedisPool = null;
private JedisPoolUtils(){
}
public static JedisPool getJedisPoolInstance() {
// 使用雙重檢查創建單例
if(null == jedisPool) {
synchronized (JedisPoolUtils.class) {
if(null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(10);
poolConfig.setMaxIdle(10);
poolConfig.setMinIdle(2);
poolConfig.setMaxWaitMillis(30*1000);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTimeBetweenEvictionRunsMillis(10*1000);
poolConfig.setMinEvictableIdleTimeMillis(30*1000);
poolConfig.setNumTestsPerEvictionRun(-1);
jedisPool = new JedisPool(poolConfig,"localhost",6379);
}
}
}
return jedisPool;
}
實例創建和釋放大致流程解析
根據流程進行源碼解析
創建過程
使用pool.getResource()
進行Jedis實例的創建。
//org.apache.commons.pool2.impl.GenericObjectPool#borrowObject(long)
public T borrowObject(final long borrowMaxWaitMillis) throws Exception {
final boolean blockWhenExhausted = getBlockWhenExhausted();
PooledObject<T> p = null;
boolean create;
final long waitTime = System.currentTimeMillis();
while (p == null) {
create = false;
// 從空閑隊列中獲取
p = idleObjects.pollFirst();
if (p == null) {
// 創建實例
p = create();
if (p != null) {
create = true;
}
}
// 吃資源是否耗盡
if (blockWhenExhausted) {
if (p == null) {
// 等待時間小於0
if (borrowMaxWaitMillis < 0) {
p = idleObjects.takeFirst();
} else {
p = idleObjects.pollFirst(borrowMaxWaitMillis,
TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException(
"Timeout waiting for idle object");
}
} else {
if (p == null) {
throw new NoSuchElementException("Pool exhausted");
}
}
if (!p.allocate()) {
p = null;
}
if (p != null) {
try {
// 重新初始化要由池返回的實例。
factory.activateObject(p);
} catch (final Exception e) {
}
}
}
updateStatsBorrow(p, System.currentTimeMillis() - waitTime);
return p.getObject();
}
釋放過程
從Jedis3.0版本后pool.returnResource()
遭棄用,官方重寫了Jedis的close方法用以代替;官方建議應用redis.clients.jedis#Jedis的close方法進行資源回收,官方代碼如下:
@Override
public void close() {
if (dataSource != null) {
JedisPoolAbstract pool = this.dataSource;
this.dataSource = null;
if (client.isBroken()) {
pool.returnBrokenResource(this);
} else {
pool.returnResource(this);
}
} else {
super.close();
}
}
這里主要看:pool.returnResource(this);
//org.apache.commons.pool2.impl.GenericObjectPool#returnObject
public void returnObject(final T obj) {
// 獲取要釋放的實例對象
final PooledObject<T> p = allObjects.get(new IdentityWrapper<>(obj));
if (p == null) {
if (!isAbandonedConfig()) {
throw new IllegalStateException(
"Returned object not currently part of this pool");
}
return; // Object was abandoned and removed
}
// 將對象標記為返回池的狀態。
markReturningState(p);
final long activeTime = p.getActiveTimeMillis();
// 這里就和上面配置的參數有關系,釋放的時候是否做連接有效性檢測(ping)
if (getTestOnReturn() && !factory.validateObject(p)) {
try {
destroy(p);
} catch (final Exception e) {
swallowException(e);
}
try {
ensureIdle(1, false);
} catch (final Exception e) {
swallowException(e);
}
updateStatsReturn(activeTime);
return;
}
// 檢查空閑對象,如果最大空閑對象數小於當前idleObjects大小,則銷毀
final int maxIdleSave = getMaxIdle();
if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
try {
destroy(p);
} catch (final Exception e) {
swallowException(e);
}
} else {
// 否則加入到空閑隊列中,空閑隊列是一個雙端隊列
// getLifo 也和配置的參數有關,默認True
if (getLifo()) {
// last in first out,加到隊頭
idleObjects.addFirst(p);
} else {
// first in first out ,加到隊尾
idleObjects.addLast(p);
}
}
updateStatsReturn(activeTime);
}
上面創建和釋放刪除了一些代碼,具體完整代碼都是在GenericObjectPool
類中。
小結,后悔有期
看完本文,應該大致對JedisPoolConfig
有了一定的了解,指定里面的一些配置參數,並且能夠基本的參數調優,以及實例資源的創建和釋放的過程。
如果感謝興趣的伙伴可以下載Jedis的源碼進行閱讀和學習,掌握了JedisPoolConfig
的配置,其他池化框架的配置也是大同小異,舉一反三! 江湖不遠,后會有期!
如果需要Reids相關的資源可以掃碼下方二維碼,里面有Redis的相關資源!
備注: 由於本人能力有限,文中若有錯誤之處,歡迎指正。
謝謝你的閱讀,如果您覺得這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你每天開心愉快!