天天看點

HTTP接口性能測試中池化實踐池化工具類實踐

上兩期文章,我分享了通用池化架構

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()
    }
}

           

用戶端

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

           

Have Fun ~ Tester !