1
架构师训练营第 7 周作业——性能压测工具
发布于: 2020 年 07 月 20 日
作业:用你熟悉的编程语言写一个web性能压测工具。
输入参数:url, 请求总数,并发数。
输出参数:平均响应时间,95%响应时间
用这个测试工具以10并发、100次请求压测www.baidu.com
public class Client { public static void main(String[] args) { int workers = 10; PressureContainer pc = new PressureContainer(); // 10个线程并发请求1000次 pc.start("https://www.baidu.com", 1000, workers); }}
public class PressureContainer { private String name = "worker"; private ExecutorService executor; private double topPart = 0.95; // 95% /** * @param url: 压测地址 * @param totalRequests: 总请求数 * @param workers:并发线程数 */ public void start(String url, int totalRequests, int workers) { AtomicInteger totalTimes = new AtomicInteger(totalRequests); AtomicInteger failCount = new AtomicInteger(); AtomicLong costCount = new AtomicLong(); try { // 初始化OkHttp,不然第1次访问会很慢 OkHttpUtil.get(url); } catch (IOException e1) { e1.printStackTrace(); } int initialCapacity = (int) (totalRequests * (1D - topPart)); // 存最慢的5%请求耗时 TopK topK = new TopK(initialCapacity); CountDownLatch countDownLatch = new CountDownLatch(workers); executor = new ThreadPoolExecutor(workers, workers, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PressureThreadFactory(name)); for (int i = 0; i < workers; i++) { PressureThread pThread = new PressureThread( new PressureRunner(url, totalTimes, costCount, failCount, countDownLatch, topK), url); executor.execute(pThread); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } long preCost = costCount.get() / totalRequests; System.out.println("Test:[" + url + "] Works:[" + workers + "] call [" + totalRequests + "] times, fail [" + failCount.get() + "] times, pre_cost:[" + preCost + "]ms, " + (int) (topPart * 100) + "% request <= [" + topK.first() + "]ms"); executor.shutdown(); }
/** * 压测线程工厂 */public class PressureThreadFactory implements ThreadFactory { public PressureThreadFactory(String name) { this.name = name; } @Override public Thread newThread(Runnable r) { return new PressureThread(r, name + "-" + created.incrementAndGet()); } private final String name; private final AtomicInteger created = new AtomicInteger();}
/** * 压测线程 */public class PressureThread extends Thread { public PressureThread(Runnable r, String name) { super(r, name); setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.err.println("THREAD[" + t.getName() + "] hava uncaughtException"); e.printStackTrace(); } }); }}public class PressureRunner implements Runnable { private String url; private AtomicInteger totalTimes; private AtomicInteger failCount; private AtomicLong costCount; private TopK topK; private CountDownLatch countDownLatch; /** * @param url * @param totalTimes:执行次数 * @param costCount: 总耗时统计 * @param failCount: 失败次数统计 * @param countDownLatch: * @param topK 用于存响应时间Topb百分之几的数据 */ public PressureRunner(String url, AtomicInteger totalTimes, AtomicLong costCount, AtomicInteger failCount, CountDownLatch countDownLatch, TopK topK) { this.url = url; this.totalTimes = totalTimes; this.costCount = costCount; this.failCount = failCount; this.countDownLatch = countDownLatch; this.topK = topK; } @Override public void run() { int times = totalTimes.decrementAndGet(); while (times >= 0) { long startTime = System.currentTimeMillis(); try { String result = OkHttpUtil.get(url); if (StringUtils.isEmpty(result)) { failCount.incrementAndGet(); } } catch (Exception e) { failCount.incrementAndGet(); } long cost = System.currentTimeMillis() - startTime; costCount.addAndGet(cost); topK.add(cost); System.out.println(Thread.currentThread().getName() + " " + times + "--->>> cost:" + cost + "ms"); try { // sleep一下,让出cpu给其它线程执行 Thread.sleep(50); } catch (InterruptedException e) { } times = totalTimes.decrementAndGet(); } countDownLatch.countDown(); }}
/** * http请求工具类 */public class OkHttpUtil { private final static OkHttpClient client = new OkHttpClient().newBuilder().retryOnConnectionFailure(true) .connectTimeout(10, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).writeTimeout(60, TimeUnit.SECONDS) .build();; public static String get(String url) throws IOException { Request request = new Request.Builder().url(url).build(); try (Response response = client.newCall(request).execute();) { if (response.isSuccessful()) { return response.body().string(); } else { return null; } } } }
/** * 以小顶堆存储部分响应时间最大的数据 */public class TopK { private int initialCapacity; private PriorityQueue<Long> minHeap; public TopK(int initialCapacity) { this.initialCapacity = initialCapacity; // 小顶堆 minHeap = new PriorityQueue<Long>(initialCapacity, new Comparator<Long>() { public int compare(Long i1, Long i2) { return (int) (i1 - i2); } }); } public synchronized void add(Long i) { if (initialCapacity > minHeap.size()) { minHeap.add(i); } else { Long top = minHeap.peek(); // 大数才加入 if (top < i) { minHeap.add(i); minHeap.remove(); } } } public Long first() { return minHeap.peek(); }}
执行结果:
Test:[https://www.baidu.com] Works:[10] call [1000] times, fail [0] times, pre_cost:[7]ms, 95% request <= [13]msTest:[https://www.baidu.com] Works:[10] call [10000] times, fail [0] times, pre_cost:[6]ms, 95% request <= [8]msTest:[https://time.geekbang.org/] Works:[10] call [1000] times, fail [0] times, pre_cost:[15]ms, 95% request <= [19]msTest:[https://time.geekbang.org/] Works:[10] call [10000] times, fail [0] times, pre_cost:[15]ms, 95% request <= [22]ms
划线
评论
复制
发布于: 2020 年 07 月 20 日阅读数: 68
版权声明: 本文为 InfoQ 作者【在野】的原创文章。
原文链接:【http://xie.infoq.cn/article/d20737a644b0ce8e168d1d313】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
在野
关注
还未添加个人签名 2012.03.11 加入
还未添加个人简介
评论