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


    上两期文章,我分享了通用池化框架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.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 !

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

  • 相关阅读:
    Mac 删除Openfire
    FMDB使用
    豆瓣restful api 状态和错误码
    豆瓣开放api
    常用文字配色方案
    电商网站参考
    HP后端跨域HEADER头
    PHP统计 图表实现方法
    PHP 全过程教程和测试网
    Ajax技术在购物车中的应用(PHP篇)
  • 原文地址:https://www.cnblogs.com/FunTester/p/16386615.html
Copyright © 2020-2023  润新知