写点什么

HTTP 接口性能测试中池化实践

作者:FunTester
  • 2022 年 6 月 17 日
  • 本文字数:3436 字

    阅读完需:约 11 分钟

上两期文章,我分享了通用池化框架commons-pool2两种不同的实现方式分别是:通用池化框架commons-pool2实践、- 通用池化框架实践之GenericKeyedObjectPool当时用了com.funtester.base.interfaces.IPooled代替了需要池化的对象类,后来想了想这个方案不咋好,直接放弃了。


今天我们分享一下 HTTP 请求的池化实践,分为两个org.apache.http.client.methods.HttpGetorg.apache.http.client.methods.HttpPost,由于代码雷同较多,会重点分享 GET 请求的实践,最后会附上 POST 请求的代码。之所以没有选用GenericKeyedObjectPool,因为这个实现类的代码注释中已经标明了可能存在性能瓶颈,我计划先测试其性能之后在做实践。

池化工具类

这里我将池化工厂类写成了内部静态类的形式,这样可以显得代码中的 Java 文件比较少,比较整洁。在工具类(包含池化和工程类)中,我并没有重写destroyObject方法,原因是现在是写框架部分,如果需要对 HTTP 请求对象进行处理,比如清除 token 信息等操作,可以写到业务类中,如果框架做了,那么适应性就比较差了。根据我的实践经验,大部分时候我们只需要重置很少一部分信息,请求头里面大多数 header 都是可以原封不到留到对象中,不会有任何影响,


package com.funtester.funpool

import com.funtester.config.PoolConstantimport org.apache.commons.pool2.BasePooledObjectFactoryimport org.apache.commons.pool2.PooledObjectimport org.apache.commons.pool2.impl.DefaultPooledObjectimport org.apache.commons.pool2.impl.GenericObjectPoolimport org.apache.commons.pool2.impl.GenericObjectPoolConfigimport org.apache.http.client.methods.HttpGetimport org.apache.logging.log4j.LogManagerimport org.apache.logging.log4j.Logger
class HttpGetPool extends PoolConstant {
private static final Logger logger = LogManager.getLogger(HttpGetPool.class);
private static GenericObjectPool<HttpGet> pool
static def init() { GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxTotal(MAX); poolConfig.setMinIdle(MIN_IDLE); poolConfig.setMaxIdle(MAX_IDLE); poolConfig.setMaxWaitMillis(MAX_WAIT_TIME); poolConfig.setMinEvictableIdleTimeMillis(MAX_IDLE_TIME); pool = new GenericObjectPool<>(new FunTester(), poolConfig); }

/** * 获取{@link org.apache.http.client.methods.HttpGet}对象 * @return */ static HttpGet get() { try { return pool.borrowObject() } catch (e) { logger.warn("获取${HttpGet.class} 失败", e) new HttpGet() } }
/** * 归还{@link org.apache.http.client.methods.HttpGet}对象 * @param httpGet * @return */ static def back(HttpGet httpGet) { pool.returnObject(httpGet) }
/** * 执行任务 * @param closure * @return */ def execute(Closure closure) { def get = get() try { closure(get) } catch (e) { logger.warn("执行任务失败", e) } finally { back(get) }
}
private static class FunTester extends BasePooledObjectFactory<HttpGet> {
@Override HttpGet create() throws Exception { return new HttpGet() }
@Override PooledObject<HttpGet> wrap(HttpGet obj) { return new DefaultPooledObject<HttpGet>(obj) } }}
复制代码

实践

服务端

依旧采用了我最喜欢的moco_FunTester框架,为了验证 URL 已经会替换我多写了几个接口,代码如下:


package com.mocofun.moco.main
import com.funtester.utils.ArgsUtilimport com.mocofun.moco.MocoServer
class Share extends MocoServer {
static void main(String[] args) { def util = new ArgsUtil(args) def server = getServerNoLog(util.getIntOrdefault(0, 12345)) server.get(urlOnly("/get")).response(jsonRes(getJson("msg=get请求", "code=0"))) server.get(urlOnly("/get1")).response(jsonRes(getJson("msg=get1请求", "code=1"))) server.get(urlOnly("/get2")).response(jsonRes(getJson("msg=get2请求", "code=2"))) server.get(urlOnly("/get3")).response(jsonRes(getJson("msg=get2请求", "code=3"))) server.get(urlOnly("/get4")).response(jsonRes(getJson("msg=get2请求", "code=4"))) server.response("Have Fun ~ Tester !") def run = run(server) waitForKey("FunTester") run.stop() }}
复制代码

客户端

这里只测试 GET 请求,大家可以注意看一下我注释掉的一行代码// get.setURI(null)就是在将 GET 请求对象归还对象池之前,做不做这个操作对实际结果并无影响。另外我自己用了非池化技术实现的请求方法也做了同样的测试和验证功能。


package com.funtest.groovytest

import com.funtester.frame.SourceCodeimport com.funtester.frame.execute.ThreadPoolUtilimport com.funtester.funpool.HttpGetPoolimport com.funtester.httpclient.FunHttp
class PoolTest extends SourceCode {
public static void main(String[] args) { def url = "http://localhost:12345/get" HttpGetPool.init() 100.times { fun { Res(url+getRandomInt(4)) Res2(url+getRandomInt(4)) } } }
/** * 使用池化技术 * @param url * @return */ static def Res(def url) { def get = HttpGetPool.get() get.setURI(new URI(url)) def response = FunHttp.getHttpResponse(get)// get.setURI(null) HttpGetPool.back(get) def integer = response.getInteger("code") if (!url.endsWith(integer as String)) { fail() } }
static def Res2(def url) { def response = FunHttp.getHttpResponse(FunHttp.getHttpGet(url)) def integer = response.getInteger("code") if (!url.endsWith(integer as String)) { fail() }
}
}
复制代码


控制台信息:


21:22:05.595 main   ###### #     #  #    # ####### ######  #####  ####### ######  #####  #      #     #  ##   #    #    #       #         #    #       #    #  ####   #     #  # #  #    #    ####    #####     #    ####    #####  #      #     #  #  # #    #    #            #    #    #       #   #  #       #####   #    #    #    ######  #####     #    ######  #    #
21:22:06.116 Deamon 守护线程开启!21:22:06.560 F-8 请求uri:http://localhost:12345/get3 , 耗时:418 ms , HTTPcode: 20021:22:06.560 F-7 请求uri:http://localhost:12345/get4 , 耗时:418 ms , HTTPcode: 200…………………………………………………………省略部分日志…………………………………………………………………………………………21:22:06.575 F-1 请求uri:http://localhost:12345/get3 , 耗时:2 ms , HTTPcode: 20021:22:06.575 F-7 请求uri:http://localhost:12345/get2 , 耗时:1 ms , HTTPcode: 20021:22:06.727 main 异步线程池等待执行1次耗时:607 ms21:22:07.157 Deamon 异步线程池关闭!
进程已结束,退出代码0
复制代码


可以看到日志中并没有报错,每个线程发起第一次请求的时候都会因为创建连接而耗时较长,后面的请求连接复用之后就非常快了。异步线程池默认大小是 8,F-2代表第二个线程,文中日志不全。

Have Fun ~ Tester !


阅读原文,跳转我的仓库地址

发布于: 刚刚阅读数: 3
用户头像

FunTester

关注

公众号:FunTester,800篇原创,欢迎关注 2020.10.20 加入

Fun·BUG挖掘机·性能征服者·头顶锅盖·Tester

评论

发布
暂无评论
HTTP接口性能测试中池化实践_FunTester_InfoQ写作社区