Using Apache Commons Pool2 GenericKeyedObjectPool for gRPC Connection Management
This article explains how to apply Apache Commons Pool2's GenericKeyedObjectPool to pool gRPC ManagedChannel objects, discusses thread‑safety and performance considerations, and provides concrete Java code for a keyed object pool factory, pool configuration, and typical acquire/return usage patterns.
In a previous post the author introduced the commons-pool2 library and its two main pool implementations: GenericObjectPool for single‑type objects and GenericKeyedObjectPool for a map‑like pool keyed by a custom identifier. The current article shows why the keyed pool is useful when a gRPC client needs to select different backend nodes based on a shard key.
The GenericKeyedObjectPool essentially stores objects in a Map<K, ObjectPool<V>> where the key is user‑defined and the value implements org.apache.commons.pool2.ObjectPool . The underlying map implementation is a thread‑safe ConcurrentHashMap . Two important notes from the source code are:
The internal map uses ConcurrentHashMap , guaranteeing thread safety.
Frequent borrow/return operations can cause performance overhead, so a dedicated benchmark is planned.
In the author's gRPC scenario, each io.grpc.ManagedChannel can serve dozens of concurrent threads, so the pool is not expected to become a bottleneck.
Poolable Class
The objects that will be pooled are the same as in the previous article (e.g., a wrapper implementing IPooled ).
Pool Factory Class
The factory must define the key and value types and extend BaseKeyedPooledObjectFactory . The following code shows a minimal abstract factory:
package com.funtester.funpool;
import com.funtester.base.interfaces.IPooled;
import org.apache.commons.pool2.BaseKeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
/**
* 可池化工厂类
*/
abstract class KeyPoolFactory
extends BaseKeyedPooledObjectFactory
{
abstract IPooled init();
@Override
IPooled create(F k) throws Exception {
return init();
}
@Override
PooledObject
wrap(IPooled obj) {
return obj.reInit();
}
@Override
void destroyObject(F key, PooledObject
p) throws Exception {
p.getObject().destory();
super.destroyObject(key, p);
}
}If the pooled objects do not hold external resources, overriding destroyObject is optional.
Object Pool
The pool itself is created with a GenericObjectPoolConfig that defines limits, idle policies, and blocking behavior. Example configuration:
package com.funtester.funpool;
import com.funtester.base.interfaces.IPooled;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
class KeyPool {
KeyPool(KeyPoolFactory factory) {
this.factory = factory;
this.pool = init();
}
private GenericKeyedObjectPool
pool = init();
private KeyPoolFactory
factory;
private GenericKeyedObjectPool
init() {
// pool configuration
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(8); // maximum total objects
poolConfig.setMinIdle(0); // minimum idle objects
poolConfig.setMaxIdle(8); // maximum idle objects
poolConfig.setMaxWaitMillis(-1); // wait indefinitely when exhausted
poolConfig.setLifo(true); // LIFO ordering
poolConfig.setMinEvictableIdleTimeMillis(1000L * 60L * 30L); // 30 min
poolConfig.setBlockWhenExhausted(true);
return new GenericKeyedObjectPool
(factory, poolConfig);
}
/**
* Acquire an object from the pool
*/
IPooled get(String key) {
try {
return pool.borrowObject("FunTester");
} catch (Exception e) {
e.printStackTrace();
}
return factory.create("FunTester");
}
/**
* Return an object to the pool
*/
void back(String key, IPooled iPooled) {
pool.returnObject("FunTester", iPooled);
}
/**
* Execute a closure with a pooled client and ensure return
*/
def execute(String key, Closure closure) {
IPooled client = get(key);
try {
closure(client);
} finally {
back(key, client);
}
}
}The author notes that the generic approach may be abandoned later due to its complexity, and plans to benchmark the two pool implementations.
Conclusion
The article demonstrates a practical way to integrate GenericKeyedObjectPool with gRPC channels, highlights thread‑safety via ConcurrentHashMap , and warns about potential performance impacts when borrowing and returning objects too frequently.
FunTester
10k followers, 1k articles | completely useless
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.