手寫java性能測試框架第二版

引言

依照領導要求區分瞭兩種壓測模式:固定次數壓測和固定時間壓測。此前一直沿用的都是固定次數,所以本次第二版剝離瞭固定次數的模式增加瞭固定時間的模式。

這是第一版:性能測試框架

第二版的threadbase代碼如下

package com.fun.base.constaint;
import com.fun.frame.SourceCode;
import java.util.concurrent.CountDownLatch;
/**
 * 多線程任務基類,可單獨使用
 */
public abstract class ThreadBase<T> extends SourceCode implements Runnable {
    /**
     * 計數鎖
     * <p>
     * 會在concurrent類裡面根據線程數自動設定
     * </p>
     */
    CountDownLatch countDownLatch;
    /**
     * 用於設置訪問資源
     */
    public T t;
    protected ThreadBase() {
        super();
    }
    /**
     * groovy無法直接訪問t,所以寫瞭這個方法
     *
     * @return
     */
    public String getT() {
        return t.toString();
    }
    /**
     * 運行待測方法的之前的準備
     */
    protected abstract void before();
    /**
     * 待測方法
     *
     * @throws Exception
     */
    protected abstract void doing() throws Exception;
    /**
     * 運行待測方法後的處理
     */
    protected abstract void after();
    public void setCountDownLatch(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
}

固定次數模式的壓測虛擬類

package com.fun.base.constaint;
import com.fun.frame.excute.Concurrent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import static com.fun.utils.Time.getTimeStamp;
/**
 * 請求時間限制的多線程類,限制每個線程執行的次數
 *
 * <p>
 * 通常在測試某項用例固定時間的場景下使用,可以提前終止測試用例
 * </p>
 *
 * @param <T> 閉包參數傳遞使用,Groovy腳本會有一些兼容問題,部分對象需要tostring獲取參數值
 */
public abstract class ThreadLimitTimes<T> extends ThreadBase {
    private static final Logger logger = LoggerFactory.getLogger(ThreadLimitTimes.class);
    /**
     * 任務請求執行次數
     */
    public int times;
    /**
     * 用於設置訪問資源
     */
    public T t;
    public ThreadLimitTimes(T t, int times) {
        this(times);
        this.t = t;
    }
    public ThreadLimitTimes(int times) {
        this();
        this.times = times;
    }
    protected ThreadLimitTimes() {
        super();
    }
    /**
     * groovy無法直接訪問t,所以寫瞭這個方法
     *
     * @return
     */
    public String getT() {
        return t.toString();
    }
    @Override
    public void run() {
        try {
            before();
            List<Long> t = new ArrayList<>();
            long ss = getTimeStamp();
            for (int i = 0; i < times; i++) {
                long s = getTimeStamp();
                doing();
                long e = getTimeStamp();
                t.add(e - s);
            }
            long ee = getTimeStamp();
            logger.info("執行次數:{},總耗時:{}", times, ee - ss);
            Concurrent.allTimes.addAll(t);
        } catch (Exception e) {
            logger.warn("執行任務失敗!", e);
        } finally {
            if (countDownLatch != null)
                countDownLatch.countDown();
            after();
        }
    }
    /**
     * 運行待測方法的之前的準備
     */
    protected abstract void before();
    /**
     * 待測方法
     *
     * @throws Exception
     */
    protected abstract void doing() throws Exception;
    /**
     * 運行待測方法後的處理
     */
    protected abstract void after();
}

固定時間模式虛擬類

package com.fun.base.constaint;
import com.fun.frame.excute.Concurrent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import static com.fun.utils.Time.getTimeStamp;
/**
 * 請求時間限制的多線程類,限制每個線程執行的時間
 * <p>
 * 通常在測試某項用例固定時間的場景下使用,可以提前終止測試用例
 * </p>
 *
 * @param <T> 閉包參數傳遞使用,Groovy腳本會有一些兼容問題,部分對象需要tostring獲取參數值
 */
public abstract class ThreadLimitTime<T> extends ThreadBase {
    /**
     * 全局的時間終止開關
     */
    private static boolean key = false;
    private static final Logger logger = LoggerFactory.getLogger(ThreadLimitTime.class);
    /**
     * 任務請求執行時間,單位是秒
     */
    public int time;
    /**
     * 用於設置訪問資源
     */
    public T t;
    public ThreadLimitTime(T t, int time) {
        this(time);
        this.t = t;
    }
    public ThreadLimitTime(int time) {
        this();
        this.time = time * 1000;
    }
    protected ThreadLimitTime() {
        super();
    }
    @Override
    public void run() {
        try {
            before();
            List<Long> t = new ArrayList<>();
            long ss = getTimeStamp();
            while (true) {
                long s = getTimeStamp();
                doing();
                long e = getTimeStamp();
                t.add(e - s);
                if ((e - ss) > time || key) break;
            }
            long ee = getTimeStamp();
            logger.info("執行時間:{} s,總耗時:{}", time / 1000, ee - ss);
            Concurrent.allTimes.addAll(t);
        } catch (Exception e) {
            logger.warn("執行任務失敗!", e);
        } finally {
            if (countDownLatch != null)
                countDownLatch.countDown();
            after();
        }
    }
    /**
     * 用於在某些情況下提前終止測試
     */
    public static void stopAllThread() {
        key = true;
    }
}

這裡我多加瞭一個終止測試的key,暫時沒有用,以防萬一。之所以沒有采用另起線程去計時原因有二:進行測試過程中無論如何都會記錄時間戳,多餘的計算比較時間戳大小消耗性能很低,可以忽略;另起線程設計麻煩,在發生意外情況時缺少第二種保險措施。

HTTPrequestbase為基礎的多線程類

下面是兩種實現類的Demo,以HTTPrequestbase作為基礎的多線程類。

固定次數模式的多線程類

/**
 * http請求多線程類
 */
public class RequestThreadTimes extends ThreadLimitTimes {
    static Logger logger = LoggerFactory.getLogger(RequestThreadTimes.class);
    /**
     * 請求
     */
    public HttpRequestBase request;
    /**
     * 單請求多線程多次任務構造方法
     *
     * @param request 被執行的請求
     * @param times   每個線程運行的次數
     */
    public RequestThreadTimes(HttpRequestBase request, int times) {
        this.request = request;
        this.times = times;
    }
    @Override
    public void before() {
        GCThread.starts();
    }
    @Override
    protected void doing() throws Exception {
        getResponse(request);
    }
    @Override
    protected void after() {
        GCThread.stop();
    }
    /**
     * 多次執行某個請求,但是不記錄日志,記錄方法用 loglong
     * <p>此方法隻適應與單個請求的重復請求,對於有業務聯系的請求暫時不能適配</p>
     *
     * @param request 請求
     * @throws IOException
     */
    void getResponse(HttpRequestBase request) throws IOException {
        CloseableHttpResponse response = ClientManage.httpsClient.execute(request);
        String content = FanLibrary.getContent(response);
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
            logger.warn("響應狀態碼:{},響應內容:{}", content, response.getStatusLine());
        response.close();
    }
}

固定時間模式的多線程類

package com.fun.frame.thead;
import com.fun.base.constaint.ThreadLimitTime;
import com.fun.frame.httpclient.ClientManage;
import com.fun.frame.httpclient.FanLibrary;
import com.fun.frame.httpclient.GCThread;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
 * http請求多線程類
 */
public class RequestThreadTime extends ThreadLimitTime {
    static Logger logger = LoggerFactory.getLogger(RequestThreadTime.class);
    /**
     * 請求
     */
    public HttpRequestBase request;
    /**
     * 單請求多線程多次任務構造方法
     *
     * @param request 被執行的請求
     * @param times   每個線程運行的次數
     */
    public RequestThreadTime(HttpRequestBase request, int time) {
        this.request = request;
        this.time = time;
    }
    @Override
    public void before() {
        GCThread.starts();
    }
    @Override
    protected void doing() throws Exception {
        getResponse(request);
    }
    @Override
    protected void after() {
        GCThread.stop();
    }
    /**
     * 多次執行某個請求,但是不記錄日志,記錄方法用 loglong
     * <p>此方法隻適應與單個請求的重復請求,對於有業務聯系的請求暫時不能適配</p>
     *
     * @param request 請求
     * @throws IOException
     */
    void getResponse(HttpRequestBase request) throws IOException {
        CloseableHttpResponse response = ClientManage.httpsClient.execute(request);
        String content = FanLibrary.getContent(response);
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
            logger.warn("響應狀態碼:{},響應內容:{}", content, response.getStatusLine());
        response.close();
    }
}

其中可以發現,差別就在於屬性time還是times的設定。

使用Demo:

package com.fun;
import com.fun.base.constaint.ThreadLimitTime;
import com.fun.frame.SourceCode;
import com.fun.frame.excute.Concurrent;
import java.util.ArrayList;
import java.util.List;
public class AR extends SourceCode {
    public static void main(String[] args) {
        ThreadLimitTime<Object> threadLimitTime = new ThreadLimitTime<Object>(10) {
            /**
             * 運行待測方法的之前的準備
             */
            @Override
            protected void before() {
            }
            /**
             * 待測方法
             *
             * @throws Exception
             */
            @Override
            protected void doing() throws Exception {
                AR.test();
            }
            /**
             * 運行待測方法後的處理
             */
            @Override
            protected void after() {
            }
        };
        new Concurrent(threadLimitTime,5).start();
        FanLibrary.testOver();
    }
    public static void test() {
        synchronized (AR.class) {
            sleep(100);
            output("fun");
        }
    }
}

剩下的mysql和redis以及dubbo的Demo就不寫瞭,各位看官看著發揮即可,更多關於java性能測試框架的資料請關註WalkonNet其它相關文章!

推薦閱讀: