序文
jedisPoolは、Apache-commons-pool2の抽象オブジェクトプール(objectPool)テクノロジーに基づいています。jedisPoolでは、接続はjedisObjectオブジェクトインスタンスです。jedisPoolのコアは次のとおりです。設定されたpoolConfigに従って、接続プールコンテナLinkedBlockingDequeの周囲で、関連するアクションで、追加と削除のロジックを実行します。したがって、以下では主に、jedisPoolの実現をスパイできるキー構成とキーメソッドの動作について説明します。
使用例
/**
* @author : kl (http://kailing.pub)
* @since : 2022-04-26 12:37
*/
public class KLMain1 {
public static void main(String[] args) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMinIdle(10);
JedisPool pool = new JedisPool(config,Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);
try (Jedis jedis = pool.getResource()) {
jedis.set("foo", "kl");
System.out.println(jedis.get("foo"));
}
}
}
キー設定の概要
jedis接続プール構成クラスはGenericObjectPoolConfigであり、サブクラスJedisPoolConfigです。通常、サブクラスJedisPoolConfigは、接続プールパラメーターを構築するために使用されます。これにより、デフォルトでredis接続のライフサイクルを管理するいくつかの構成が初期化されます。
プール容量
プールサイズを制御し、接続を取得するための戦略を構成します
名前
|
デフォルト
|
概要
|
---|---|---|
maxTotal | 8 | 接続の最大数 |
maxIdle | 8 | アイドル状態の接続の最大数 |
minIdle | 0 | アイドル接続の最小数 |
lifo |
true | デフォルトでキューの先頭に戻される接続を返すときのポリシー |
blockWhenExhausted | true | 接続を取得するときに、接続プールが空のときに接続の待機をブロックするかどうか |
maxWaitDuration | -1 | blockWhenExhausted = trueの場合にのみ有効になり、ブロックを待機する時間を示します。-1は、接続が取得されるまでブロックすることを意味します |
testOnCreate | false | 接続の作成時に接続の有効性を確認するかどうか、接続が無効な場合はnullを返します |
testOnBorrow | false | 接続を借りるときに接続の有効性を確認するかどうかにかかわらず、無効な接続は直接恍惚状態になり、取得を継続しようとします |
testOnReturn | false | 接続を返すときに接続の有効性を確認するかどうか、無効なリンクは直接破棄され、新しい接続が作成されて返されます |
ライフサイクル
commons-pool-evictorという名前のスレッドをアイドル接続、無効な接続の検出に対して有効にするかどうかを制御します。および接続検出を構成するためのポリシー
名前
|
GenericObjectPoolConfigのデフォルト値
|
jedisPoolConfigのデフォルト値
|
概要
|
---|---|---|---|
testWhileIdle |
false | true | エビクションラインが有効になっているときに無効なリンクを検出するかどうか |
durationBetweenEvictionRuns |
-1 | 30代 | エビクションスレッドの各検出間の時間間隔(ミリ秒単位)。-1は、エビクションスレッドを開始しないことを意味します |
numTestsPerEvictionRun |
3 | -1 | 毎回チェックするリンクの数、-1はすべてをチェックすることを意味します |
minEvictableIdleDuration |
30分 | 60代 | 接続を削除する場合、接続の最小アイドル時間は存続します |
softMinEvictableIdleDuration |
-1 | -1 | ソフトエビクション中の最小アイドル時間、-1はintMaxを意味します |
キーロジック
以下のコードはすべてGenericObjectPoolクラスに含まれています。各メソッドで直接使用される主要なメソッド定義と構成項目の例を次に示します。このパートを読むと、通常構成されているパラメーターがどのように機能するかを理解できます。以下に掲載されているソースコードは、スペース上の理由により削除されており、主要なロジック部分のみが保持され、強調表示されています。
メソッド定義
|
使用した構成項目
|
概要
|
---|---|---|
PooledObject <T> create() | maxTotal、testOnCreate | 接続を作成する |
T BorrowObject(final long BorrowMaxWaitMillis) | blockWhenExhausted、maxWaitDuration、testOnBorrow | リース接続 |
void returnObject(final T obj) | maxIdle、lifo、testOnReturn | リターン接続 |
void evict() | testWhileIdle、durationBetweenEvictionRuns、numTestsPerEvictionRun、minEvictableIdleDuration、softMinEvictableIdleDuration、minIdle | 立ち退き接続 |
void sureMinIdle() | lifo、minIdle | 最小限のアイドル接続を維持する |
void addObject() | lifo | 接続プールに接続を追加します |
作成
private PooledObject<T> create() throws Exception {
int localMaxTotal = getMaxTotal();
if (localMaxTotal < 0) {
localMaxTotal = Integer.MAX_VALUE;
}
Boolean create = null;
while (create == null) {
synchronized (makeObjectCountLock) {
final long newCreateCount = createCount.incrementAndGet();
if (newCreateCount > localMaxTotal) {
// 池当前处于满负荷状态或正在制造足够的新对象以使其达到满负荷状态。
createCount.decrementAndGet();
if (makeObjectCount == 0) {
// 没有正在进行的 makeObject() 调用,因此池已满。不要尝试创建新对象。
create = Boolean.FALSE;
} else {
// 正在进行的 makeObject() 调用可能会使池达到容量。这些调用也可能会失败,因此请等待它们完成,然后重新测试池是否已满负荷
makeObjectCountLock.wait(localMaxWaitTimeMillis);
}
} else {
// The pool is not at capacity. Create a new object.
makeObjectCount++;
create = Boolean.TRUE;
}
}
// Do not block more if maxWaitTimeMillis is set.
if (create == null &&
(localMaxWaitTimeMillis > 0 &&
System.currentTimeMillis() - localStartTimeMillis >= localMaxWaitTimeMillis)) {
create = Boolean.FALSE;
}
}
if (!create.booleanValue()) {
return null;
}
final PooledObject<T> p;
try {
p = factory.makeObject();
if (getTestOnCreate() && !factory.validateObject(p)) {
createCount.decrementAndGet();
return null;
}
}
//...省略
return p;
}
接続の作成は低レベルのメソッドであり、接続のリース、接続の返却、およびアイドル状態の接続の維持に使用されます。接続を作成する前に、現在の接続がmaxTotalに達しているかどうかを判断します。maxTotalに達し、この時点で接続が作成されていない場合は、プールがいっぱいでnullを返します。testOnCreateの構成に従って、接続が正常に作成された場合、testOnCreate = trueの場合、切断が使用可能です。接続が無効な場合でも、nullが返されます。
BorrowObject
public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
assertOpen();
final AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) &&
(getNumActive() > getMaxTotal() - 3)) {
removeAbandoned(ac);
}
PooledObject<T> p = null;
// Get local copy of current config so it is consistent for entire
// method execution
final boolean blockWhenExhausted = getBlockWhenExhausted();
boolean create;
final long waitTimeMillis = 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) {
if (borrowMaxWaitDuration.isNegative()) {
p = idleObjects.takeFirst();
} else {
p = idleObjects.pollFirst(borrowMaxWaitDuration);
}
}
if (p == null) {
throw new NoSuchElementException(appendStats(
"Timeout waiting for idle object, borrowMaxWaitDuration=" + borrowMaxWaitDuration));
}
} else if (p == null) {
throw new NoSuchElementException(appendStats("Pool exhausted"));
}
if (!p.allocate()) {
p = null;
}
if (p != null) {
try {
factory.activateObject(p);
} catch (final Exception e) {
try {
destroy(p, DestroyMode.NORMAL);
} catch (final Exception e1) {
// Ignore - activation failure is more important
}
p = null;
if (create) {
final NoSuchElementException nsee = new NoSuchElementException(
appendStats("Unable to activate object"));
nsee.initCause(e);
throw nsee;
}
}
if (p != null && getTestOnBorrow()) {
boolean validate = false;
Throwable validationThrowable = null;
try {
validate = factory.validateObject(p);
} catch (final Throwable t) {
PoolUtils.checkRethrow(t);
validationThrowable = t;
}
if (!validate) {
try {
destroy(p, DestroyMode.NORMAL);
destroyedByBorrowValidationCount.incrementAndGet();
} catch (final Exception e) {
// Ignore - validation failure is more important
}
p = null;
if (create) {
final NoSuchElementException nsee = new NoSuchElementException(
appendStats("Unable to validate object"));
nsee.initCause(validationThrowable);
throw nsee;
}
}
}
}
}
updateStatsBorrow(p, Duration.ofMillis(System.currentTimeMillis() - waitTimeMillis));
return p.getObject();
}
リースされた接続は、接続プールコンテナリストの先頭から取得されます。nullの場合は、create()が呼び出されて接続が作成されます。それでもnullの場合は、次の設定に従って接続を待機するかどうかが決定されます。 blockWhenExhausted = true、maxWaitDuration = -1で常に待機します。それ以外の場合は、maxWaitDurationの時間待機します。待機後に接続が取得されない場合、[アイドルオブジェクトの待機タイムアウト]の例外がスローされます。blockWhenExhausted = falseの場合、接続プールに使用可能な接続がない場合、接続プールがいっぱいです[プールが使い果たされました]例外がすぐにスローされます。接続が正常に取得された後、testOnBorrow = trueの場合、接続が有効かどうかの判断を続け、無効なリンクは直接破棄され、使用可能な接続が取得されるまで上記の操作を繰り返します。
returnObject
public void returnObject(final T obj) {
final PooledObject<T> p = getPooledObject(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 Duration activeTime = p.getActiveDuration();
if (getTestOnReturn() && !factory.validateObject(p)) {
try {
destroy(p, DestroyMode.NORMAL);
} catch (final Exception e) {
swallowException(e);
}
try {
ensureIdle(1, false);
} catch (final Exception e) {
swallowException(e);
}
updateStatsReturn(activeTime);
return;
}
try {
factory.passivateObject(p);
} catch (final Exception e1) {
swallowException(e1);
try {
destroy(p, DestroyMode.NORMAL);
} catch (final Exception e) {
swallowException(e);
}
try {
ensureIdle(1, false);
} catch (final Exception e) {
swallowException(e);
}
updateStatsReturn(activeTime);
return;
}
if (!p.deallocate()) {
throw new IllegalStateException(
"Object has already been returned to this pool or is invalid");
}
final int maxIdleSave = getMaxIdle();
if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
try {
destroy(p, DestroyMode.NORMAL);
} catch (final Exception e) {
swallowException(e);
}
try {
ensureIdle(1, false);
} catch (final Exception e) {
swallowException(e);
}
} else {
if (getLifo()) {
idleObjects.addFirst(p);
} else {
idleObjects.addLast(p);
}
if (isClosed()) {
// Pool closed while object was being added to idle objects.
// Make sure the returned object is destroyed rather than left
// in the idle object pool (which would effectively be a leak)
clear();
}
}
updateStatsReturn(activeTime);
}
接続を返すことは、接続オブジェクトを接続プールコンテナに戻すことです。接続を返すときに、testOnReturn = trueの場合、接続の有効性が検証され、無効なリンクが直接破棄され、少なくとも1つのアイドル状態の接続が保証されます。次に、maxIdleがいっぱいかどうかを判断し、現在の接続がいっぱいの場合は破棄し、アイドル状態の接続が少なくとも1つあることを確認します。そうでない場合、接続はlifo = trueに従ってリンクリストの先頭に戻ります。そうでない場合は、リンクリストの末尾に戻ります。ここで、lifoは、リースされた接続が最新の接続であるか、最も古い接続であるかを制御するために使用されます。通常、デフォルトでは最新の接続が使用されます。
立ち退き
public void evict() throws Exception {
assertOpen();
if (!idleObjects.isEmpty()) {
PooledObject<T> underTest = null;
final EvictionPolicy<T> evictionPolicy = getEvictionPolicy();
synchronized (evictionLock) {
final EvictionConfig evictionConfig = new EvictionConfig(
getMinEvictableIdleDuration(),
getSoftMinEvictableIdleDuration(),
getMinIdle());
final boolean testWhileIdle = getTestWhileIdle();
for (int i = 0, m = getNumTests(); i < m; i++) {
if (evictionIterator == null || !evictionIterator.hasNext()) {
evictionIterator = new EvictionIterator(idleObjects);
}
if (!evictionIterator.hasNext()) {
// Pool exhausted, nothing to do here
return;
}
try {
underTest = evictionIterator.next();
} catch (final NoSuchElementException nsee) {
// Object was borrowed in another thread
// Don't count this as an eviction test so reduce i;
i--;
evictionIterator = null;
continue;
}
if (!underTest.startEvictionTest()) {
// Object was borrowed in another thread
// Don't count this as an eviction test so reduce i;
i--;
continue;
}
// User provided eviction policy could throw all sorts of
// crazy exceptions. Protect against such an exception
// killing the eviction thread.
boolean evict;
try {
evict = evictionPolicy.evict(evictionConfig, underTest,
idleObjects.size());
} catch (final Throwable t) {
// Slightly convoluted as SwallowedExceptionListener
// uses Exception rather than Throwable
PoolUtils.checkRethrow(t);
swallowException(new Exception(t));
// Don't evict on error conditions
evict = false;
}
if (evict) {
destroy(underTest, DestroyMode.NORMAL);
destroyedByEvictorCount.incrementAndGet();
} else {
if (testWhileIdle) {
boolean active = false;
try {
factory.activateObject(underTest);
active = true;
} catch (final Exception e) {
destroy(underTest, DestroyMode.NORMAL);
destroyedByEvictorCount.incrementAndGet();
}
if (active) {
boolean validate = false;
Throwable validationThrowable = null;
try {
validate = factory.validateObject(underTest);
} catch (final Throwable t) {
PoolUtils.checkRethrow(t);
validationThrowable = t;
}
if (!validate) {
destroy(underTest, DestroyMode.NORMAL);
destroyedByEvictorCount.incrementAndGet();
if (validationThrowable != null) {
if (validationThrowable instanceof RuntimeException) {
throw (RuntimeException) validationThrowable;
}
throw (Error) validationThrowable;
}
} else {
try {
factory.passivateObject(underTest);
} catch (final Exception e) {
destroy(underTest, DestroyMode.NORMAL);
destroyedByEvictorCount.incrementAndGet();
}
}
}
}
if (!underTest.endEvictionTest(idleObjects)) {
// TODO - May need to add code here once additional
// states are used
}
}
}
}
}
final AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
removeAbandoned(ac);
}
}
ここに投稿されたコードは全体像ではなく、主要なロジックのみが傍受されます。jedisPoolが初期化されると、durationBetweenEvictionRunsの構成に従ってcommons-pool-evictorという名前のスレッドスケジューリングが開始されます。durationBetweenEvictionRuns= -1の場合、有効にならず、evictのすべてのロジックが実行されません。それ以外の場合、エビクションロジックは実行されません。 EvictionRuns時間の間ごとに実行されます。numTestsPerEvictionRunは、毎回チェックされる接続の数を制御します。現在の接続を解除する必要があるかどうかを確認するたびに、次の2つの戦略があり、そのうちの1つを満たすことができます。
- ポリシーA:構成されたsoftMinEvictableIdleDurationが現在の接続のアイドル時間よりも短い[および]構成されたminIdleがアイドル接続の総数よりも少ない
- 戦略B:構成されたminEvictableIdleDurationが、現在の接続のアイドル時間よりも短い
通常、デフォルトのパラメーターでは、戦略Aはトリガーされません。戦略Bをトリガーするのは簡単です。現在の接続が切断された場合は、次の接続判定に進みます。それ以外の場合、testWhileIdle = trueの場合、現在の接続の有効性を引き続き判断すると、無効なリンクが破棄され、接続プールが削除されます。一連の検出後、ensureMinIdle()が呼び出され、接続プール内のアイドル接続が最小限に抑えられます。
sureMinIdle
private void ensureIdle(final int idleCount, final boolean always) throws Exception {
if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
return;
}
while (idleObjects.size() < idleCount) {
final PooledObject<T> p = create();
if (p == null) {
// Can't create objects, no reason to think another call to
// create will work. Give up.
break;
}
if (getLifo()) {
idleObjects.addFirst(p);
} else {
idleObjects.addLast(p);
}
}
if (isClosed()) {
// Pool closed while object was being added to idle objects.
// Make sure the returned object is destroyed rather than left
// in the idle object pool (which would effectively be a leak)
clear();
}
}
sureMinIdleは最終的にensureIdle()メソッドを呼び出し、idleCount入力パラメーターは構成されたminIdleです。ここでは、接続プールのアイドル接続がidleCount値に達するまで、現在のアイドル接続が着信idleCountと比較されます。
addObject
public void addObject() throws Exception {
assertOpen();
if (factory == null) {
throw new IllegalStateException("Cannot add objects without a factory.");
}
addIdleObject(create());
}
接続を作成して接続プールに追加します。このメソッドは通常、接続プールの初期化に使用されます。たとえば、接続プールは事前に予熱されています。
エピローグ
- ほとんどの人は通常、接続ライフサイクルの構成を無視して、接続プール容量の構成にのみ焦点を合わせます
私を含めて、接続プールがminIdle値を保証するという誤った認識が常にあり、現在適用されている接続がminIdle接続プールよりも小さい限り、新しい接続を作成するためにスケーリングされません。ソースコードを調べて初めて、エビクション接続スレッドが有効になっている限り、アイドル状態の存続時間が構成された値minEvictableIdleDurationよりも長い限り、それが削除され、新しい接続が実行されることがわかりました。 minIdleに従って再構築されます。また、jedisPoolConfigのminEvictableIdleDurationのデフォルトは60秒であるため、minIdle構成が実際のニーズよりもはるかに大きい場合、デフォルトの接続取得戦略はlifo(常に最新の接続を取得)であるため、常に新しく作成された接続を取得して破棄し、サイクルでそれを作成し続けます。トラフィックが突然増加すると、minIdleの接続数が接続プールに保持されない可能性があり、多数の接続を突然作成する必要があります。これは、必然的にアプリケーションに一定の影響を及ぼします。ここでは、エビクションスレッドが有効になっている場合、作成された接続が維持されるように、minEvictableIdleDurationの値を少し大きくするか、直接-1に設定することをお勧めします。