通用池化框架commonspool2实践
最近在学习使用gRPC的知识过程中,突然发现了gRPC并没有提供一个类似于HttpClient连接池管理的功能,所以搜了一下相关资料,然后发现了一个通用的池化框架 commons-pool2 。从Go语言说起
对于池化技术相信大家都经说过,对于Java来讲:线程池,对于HttpClient:连接池。之前我是一直只是使用,第一次见证到池化技术的威力是在学习Go语言的HTTP接口测试常用的两个库: net/http 和fasthttp 。
下面是 net/http 创建HTTP请求的方法封装:// Get 获取GET请求 // @Description: // @param uri // @param args // @return *fhttp.Request func Get(uri string, args map[string]interface{}) *http.Request { if args != nil { uri = uri + "?" + ToValues(args) } request, _ := http.NewRequest("GET", uri, nil) return request }
下面是 fasthttp 的创建HTTP请求的方法封装:func DoGet(url string, args map[string]interface{}) ([]byte, error) { req := fasthttp.AcquireRequest() defer fasthttp.ReleaseRequest(req) // 用完需要释放资源 req.Header.SetMethod("GET") values := ToValues(args) req.SetRequestURI(url + "?" + values) resp := fasthttp.AcquireResponse() defer fasthttp.ReleaseResponse(resp) // 用完需要释放资源 if err := FastClient.Do(req, resp); err != nil { fmt.Println("请求失败:", err.Error()) return nil, err } return resp.Body(), nil }
其中 fasthttp 两行:req := fasthttp.AcquireRequest() defer fasthttp.ReleaseRequest(req) resp := fasthttp.AcquireResponse() defer fasthttp.ReleaseResponse(resp)
这就是常用的池化技术使用规范,先获取一个,然后用完之后还回去。
据资料显示,两者性能差异最大的原因就是 fasthttp 采用了对象池化技术。一下勾起我的好奇心,不过并没有采取行动,还以为是Go语言的奇技淫巧。后来想曾经想过自己实现一个对象池,后来由于技术不够放弃了,原因是性能测试框架已经满足了设计标准,有了阶段性的成果。commons-pool2
Apache Commons Pool库提供了一整套用于实现对象池化的API,以及若干种各具特色的对象池实现。2.0版本,并非是对1.x的简单升级,而是一个完全重写的对象池的实现,显著的提升了性能和可伸缩性,并且包含可靠的实例跟踪和池监控。
这个是偶然发现的,没想到真实我孤陋寡闻了。既然挂上了Apache名字,就知道这是一个非常成熟的框架,所以果断学习起来。首先从gRPC的测试代码中剥离。我把使用分成了三部分。 可池化类
首先我们需要一个可以被池化的对象,也可以是一组对象,这里我只分享前者。我写了一个接口 com.funtester.base.interfaces.IPooled :package com.funtester.base.interfaces import org.apache.commons.pool2.PooledObject interface IPooled { PooledObject reInit() void destory() } 池化工厂类
然后我们需要一个池化工厂类,这个类主要解决如何创建可池化对象,如何将池化对象包装成 org.apache.commons.pool2.PooledObject ,这个是对象池直接存储的对象,还有一个摧毁的com.funtester.funpool.FunPoolFactory#destroyObject 方法。package com.funtester.funpool import com.funtester.base.interfaces.IPooled import org.apache.commons.pool2.BasePooledObjectFactory import org.apache.commons.pool2.PooledObject /** * 可池化工厂类 */ abstract class FunPoolFactory extends BasePooledObjectFactory { abstract IPooled init() @Override IPooled create() throws Exception { init() } @Override PooledObject wrap(IPooled obj) { return obj.reInit() } @Override void destroyObject(PooledObject p) throws Exception { p.getObject().destory() super.destroyObject(p) } } 对象池
这个算是简单的,设置几个常用的配置项,然后创建对象池。 package com.funtester.funpool import com.funtester.base.interfaces.IPooled import org.apache.commons.pool2.impl.GenericObjectPool import org.apache.commons.pool2.impl.GenericObjectPoolConfig class FunPool { private static GenericObjectPool pool = init(); private static FunPoolFactory factory private static GenericObjectPool init() { // 连接池的配置 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); // 池中的最大连接数 poolConfig.setMaxTotal(8); // 最少的空闲连接数 poolConfig.setMinIdle(0); // 最多的空闲连接数 poolConfig.setMaxIdle(8); // 当连接池资源耗尽时,调用者最大阻塞的时间,超时时抛出异常 单位:毫秒数 poolConfig.setMaxWaitMillis(-1); // 连接池存放池化对象方式,true放在空闲队列最前面,false放在空闲队列最后 poolConfig.setLifo(true); // 连接空闲的最小时间,达到此值后空闲连接可能会被移除,默认即为30分钟 poolConfig.setMinEvictableIdleTimeMillis(1000L * 60L * 30L); // 连接耗尽时是否阻塞,默认为true poolConfig.setBlockWhenExhausted(true); // 连接池创建 return new GenericObjectPool<>(factory, poolConfig); } }
然后我们就可以使用这个对象池了,我定义了两个方法来演示两种常见的场景: /** * 从连接池获取对象 */ static IPooled get() { try { return pool.borrowObject(); } catch (Exception e) { e.printStackTrace(); } return factory.create(); } /** * 执行器 */ static def execute(Closure closure) { IPooled client = get(); try { closure(client); } finally { pool.returnObject(client); } }
当然这个演示的Demo是非常不优雅的,而且缺少拓展性,后面我会继续优化。 Have Fun ~ Tester !