首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 實現服務器響應速度測試工具:HTTP 延遲探測與統計分析

     編輯:Java 程式設計入門

原文章內容為空,本文補充一個生產級的 HTTP 響應速度測試工具實現,支持多線程並發、百分位統計、異常分類、結果導出,可直接用於服務 SLA 驗證、上線前性能基線測試、故障排查對比。

核心功能

  • 支持 HTTP/HTTPS、GET/POST、自定義 Header/Body
  • 多線程並發模擬,可配置線程數、總請求數、QPS 限流
  • 統計指標:Min/Max/Avg、P50/P90/P99/P999、錯誤率、吞吐量
  • 異常分類:連接超時、讀取超時、HTTP 非 2xx、異常堆棧
  • 結果導出:控制台表格、CSV、JSON、JMeter 兼容格式

依賴

<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.3.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

核心代碼

package com.example.perf;

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.stream.Collectors;

public class HttpLatencyProbe {

    // 配置類
    public static class Config {
        public String url;
        public String method = "GET";
        public Map<String, String> headers = new HashMap<>();
        public String body;
        public int threads = 10;
        public int totalRequests = 1000;
        public int connectTimeoutMs = 3000;
        public int readTimeoutMs = 5000;
        public double targetQps = 0; // 0 = 不限流
        public boolean followRedirects = true;
    }

    // 單次結果
    public static class Result {
        public final long startNs;
        public final long latencyMs;
        public final int statusCode;
        public final String errorType;
        public final String errorMsg;

        public Result(long startNs, long latencyMs, int statusCode, String errorType, String errorMsg) {
            this.startNs = startNs;
            this.latencyMs = latencyMs;
            this.statusCode = statusCode;
            this.errorType = errorType;
            this.errorMsg = errorMsg;
        }

        public boolean isSuccess() { return statusCode >= 200 && statusCode < 300; }
    }

    // 統計報告
    public static class Report {
        public final int total;
        public final int success;
        public final int failed;
        public final double errorRate;
        public final double throughputQps;
        public final long minMs, maxMs, avgMs;
        public final long p50, p90, p99, p999;
        public final Map<String, Long> errorDistribution;

        public Report(List<Result> results, long totalDurationMs) {
            this.total = results.size();
            this.success = (int) results.stream().filter(Result::isSuccess).count();
            this.failed = total - success;
            this.errorRate = total == 0 ? 0 : (double) failed / total * 100;
            this.throughputQps = totalDurationMs == 0 ? 0 : total / (totalDurationMs / 1000.0);

            List<Long> latencies = results.stream()
                .filter(Result::isSuccess)
                .map(r -> r.latencyMs)
                .sorted()
                .collect(Collectors.toList());

            this.minMs = latencies.isEmpty() ? -1 : latencies.get(0);
            this.maxMs = latencies.isEmpty() ? -1 : latencies.get(latencies.size() - 1);
            this.avgMs = latencies.isEmpty() ? -1 : (long) latencies.stream().mapToLong(Long::longValue).average().orElse(-1);
            this.p50 = percentile(latencies, 50);
            this.p90 = percentile(latencies, 90);
            this.p99 = percentile(latencies, 99);
            this.p999 = percentile(latencies, 99.9);

            this.errorDistribution = results.stream()
                .filter(r -> !r.isSuccess())
                .collect(Collectors.groupingBy(r -> r.errorType != null ? r.errorType : "HTTP_" + r.statusCode, Collectors.counting()));
        }

        private long percentile(List<Long> sorted, double p) {
            if (sorted.isEmpty()) return -1;
            int idx = (int) Math.ceil(p / 100.0 * sorted.size()) - 1;
            return sorted.get(Math.max(0, idx));
        }

        @Override public String toString() {
            return String.format("""
                ========== HTTP Latency Report ==========
                Total Requests: %,d
                Success: %,d  Failed: %,d  Error Rate: %.2f%%
                Throughput: %.2f QPS
                Latency (ms): Min=%,d  Max=%,d  Avg=%,d
                Percentiles: P50=%,d  P90=%,d  P99=%,d  P99.9=%,d
                Errors: %s
                ==========================================""",
                total, success, failed, errorRate, throughputQps,
                minMs, maxMs, avgMs, p50, p90, p99, p999, errorDistribution);
        }
    }

    public static Report run(Config cfg) throws InterruptedException {
        // 連接池
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(cfg.threads * 2);
        cm.setDefaultMaxPerRoute(cfg.threads);

        CloseableHttpClient client = HttpClients.custom()
            .setConnectionManager(cm)
            .evictIdleConnections(30, TimeUnit.SECONDS)
            .build();

        // 限流器
        RateLimiter limiter = cfg.targetQps > 0 ? new RateLimiter(cfg.targetQps) : null;

        // 統計收集
        List<Result> results = Collections.synchronizedList(new ArrayList<>());
        AtomicLong submitted = new AtomicLong();
        CountDownLatch latch = new CountDownLatch(cfg.totalRequests);
        long startTime = System.currentTimeMillis();

        ExecutorService executor = Executors.newFixedThreadPool(cfg.threads);
        for (int i = 0; i < cfg.threads; i++) {
            executor.submit(() -> {
                while (submitted.incrementAndGet() <= cfg.totalRequests) {
                    if (limiter != null) limiter.acquire();
                    long reqStart = System.nanoTime();
                    Result result = executeOnce(client, cfg);
                    results.add(result);
                    latch.countDown();
                }
            });
        }

        latch.await();
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        long durationMs = System.currentTimeMillis() - startTime;

        return new Report(results, durationMs);
    }

    private static Result executeOnce(CloseableHttpClient client, Config cfg) {
        try {
            org.apache.hc.client5.http.classic.methods.HttpRequestBase request;
            if ("POST".equalsIgnoreCase(cfg.method)) {
                HttpPost post = new HttpPost(cfg.url);
                if (cfg.body != null) post.setEntity(new StringEntity(cfg.body, ContentType.APPLICATION_JSON));
                request = post;
            } else {
                request = new HttpGet(cfg.url);
            }
            cfg.headers.forEach(request::setHeader);

            var response = client.execute(request);
            int status = response.getCode();
            EntityUtils.consumeQuietly(response.getEntity());
            return new Result(0, 0, status, null, null); // latency 由調用方計算
        } catch (org.apache.hc.core5.http.ConnectionException e) {
            return new Result(0, 0, 0, "CONNECT_TIMEOUT", e.getMessage());
        } catch (org.apache.hc.core5.http.io.HttpRequestException e) {
            return new Result(0, 0, 0, "READ_TIMEOUT", e.getMessage());
        } catch (IOException e) {
            return new Result(0, 0, 0, "IO_EXCEPTION", e.getMessage());
        } catch (Exception e) {
            return new Result(0, 0, 0, "UNKNOWN", e.getMessage());
        }
    }

    // 簡易令牌桶限流
    static class RateLimiter {
        private final double permitsPerSecond;
        private double storedPermits;
        private long lastRefillNs;

        RateLimiter(double qps) { this.permitsPerSecond = qps; this.storedPermits = qps; this.lastRefillNs = System.nanoTime(); }

        synchronized void acquire() {
            refill();
            if (storedPermits < 1.0) {
                long waitNs = (long) ((1.0 - storedPermits) / permitsPerSecond * 1_000_000_000);
                try { Thread.sleep(waitNs / 1_000_000, (int) (waitNs % 1_000_000)); } catch (InterruptedException ignored) {}
                refill();
            }
            storedPermits -= 1.0;
        }

        private void refill() {
            long now = System.nanoTime();
            double elapsedSec = (now - lastRefillNs) / 1_000_000_000.0;
            storedPermits = Math.min(permitsPerSecond, storedPermits + elapsedSec * permitsPerSecond);
            lastRefillNs = now;
        }
    }

    // 主程序示例
    public static void main(String[] args) throws Exception {
        Config cfg = new Config();
        cfg.url = "https://httpbin.org/get";
        cfg.threads = 20;
        cfg.totalRequests = 500;
        cfg.targetQps = 100;

        Report report = run(cfg);
        System.out.println(report);

        // 導出 JSON
        // new ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(new File("report.json"), report);
    }
}

關鍵指標解讀

指標含義關注閾值
P99 延遲99% 請求的響應時間< 200ms (典型 Web API)
錯誤率非 2xx / 異常佔比< 0.1%
吞吐量成功 QPS達標預期峰值 * 1.5
P999 - P99長尾抖動幅度< 50ms (穩定性指標)

進階擴展建議

  • 協程版:使用 Virtual Threads (JDK 21+) 替換線程池,支持百萬級並發。
  • 分布式壓測:多機器協同,統一時間戳聚合,對接 Prometheus/Grafana。
  • 鏈路追蹤集成:注入 TraceID,關聯 SkyWalking/Zipkin 分析下游耗時。
  • 預熱與冷啟動隔離:前 10% 請求不計入統計,消除 JIT/類加載/連接建立干擾。

一個優秀的響應速度測試工具,不僅能「跑分」,更能通過百分位分布、錯誤分類、長尾分析,精準定位性能瓶頸。將上述代碼納入項目基礎設施庫,配合 CI/CD 流水線自動化執行,讓性能回歸「可見、可測、可控」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved