上兩期文章,我分享了通用池化框架commons-pool2
兩種不同的實現方式分別是:通用池化框架commons-pool2實踐、- 通用池化框架實踐之GenericKeyedObjectPool
當時用了com.funtester.base.interfaces.IPooled
代替了需要池化的對象類,後來想了想這個方案不咋好,直接放棄了。
今天我們分享一下HTTP請求的池化實踐,分爲兩個org.apache.http.client.methods.HttpGet
和org.apache.http.client.methods.HttpPost
,由於代碼雷同較多,會重點分享GET請求的實踐,最後會附上POST請求的代碼。之所以沒有選用GenericKeyedObjectPool
,因爲這個實現類的代碼註釋中已經標明瞭可能存在性能瓶頸,我計劃先測試其性能之後在做實踐。
池化工具類
這裏我將池化工廠類寫成了內部靜態類的形式,這樣可以顯得代碼中的Java文件比較少,比較整潔。在工具類(包含池化和工程類)中,我並沒有重寫destroyObject
方法,原因是現在是寫框架部分,如果需要對HTTP請求對象進行處理,比如清除token信息等操作,可以寫到業務類中,如果框架做了,那麼適應性就比較差了。根據我的實踐經驗,大部分時候我們只需要重置很少一部分信息,請求頭裏面大多數header都是可以原封不到留到對象中,不會有任何影響,
package com.funtester.funpool
import com.funtester.config.PoolConstant
import org.apache.commons.pool2.BasePooledObjectFactory
import org.apache.commons.pool2.PooledObject
import org.apache.commons.pool2.impl.DefaultPooledObject
import org.apache.commons.pool2.impl.GenericObjectPool
import org.apache.commons.pool2.impl.GenericObjectPoolConfig
import org.apache.http.client.methods.HttpGet
import org.apache.logging.log4j.LogManager
import 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.ArgsUtil
import 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.SourceCode
import com.funtester.frame.execute.ThreadPoolUtil
import com.funtester.funpool.HttpGetPool
import 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: 200
21: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: 200
21:22:06.575 F-7 請求uri:http://localhost:12345/get2 , 耗時:1 ms , HTTPcode: 200
21:22:06.727 main 異步線程池等待執行1次耗時:607 ms
21:22:07.157 Deamon 異步線程池關閉!
進程已結束,退出代碼0
可以看到日誌中並沒有報錯,每個線程發起第一次請求的時候都會因爲創建連接而耗時較長,後面的請求連接複用之後就非常快了。異步線程池默認大小是8,F-2
代表第二個線程,文中日誌不全。
Have Fun ~ Tester !
- 性能測試專題
- Java、Groovy、Go、Python
- FunTester社羣風采
- 測試理論雞湯
- 接口功能測試專題
- FunTester視頻專題
- 案例分享:方案、BUG、爬蟲
- UI自動化專題
- 測試工具專題
閱讀原文,跳轉我的倉庫地址