基于Java+Apache Commons Math计算随时间变化的平均请求时长
实现随时间变化的平均请求时长(仿Prometheus rate计算逻辑)
核心逻辑说明
Prometheus中rate(http_server_requests_seconds_sum[5m]) / rate(http_server_requests_seconds_count[5m])的本质是:取指定时间窗口内的总请求时长,除以该窗口内的总请求数。因为rate(sum)是窗口总时长除以窗口时间(得到每秒总时长速率),rate(count)是窗口总请求数除以窗口时间(得到每秒请求数速率),两者相除后窗口时间抵消,最终结果就是窗口内的平均请求时长。
Java实现方案
由于你之前用EmpiricalDistribution仅统计了请求时间的分桶请求数,现在需要同时统计每个时间桶内的请求数和请求时长总和,才能计算平均时长。以下是适配你离线数据集的实现代码:
1. 定义桶统计数据类
用来存储每个时间桶的关键指标:
class BucketStats { private final long startTimeMs; private final long endTimeMs; private long requestCount; private double totalDurationSec; public BucketStats(long startTimeMs, long endTimeMs) { this.startTimeMs = startTimeMs; this.endTimeMs = endTimeMs; this.requestCount = 0; this.totalDurationSec = 0.0; } public void recordRequest(double durationSec) { requestCount++; totalDurationSec += durationSec; } public double getAverageDurationSec() { return requestCount == 0 ? 0.0 : totalDurationSec / requestCount; } // Getter方法 public long getStartTimeMs() { return startTimeMs; } public long getEndTimeMs() { return endTimeMs; } public long getRequestCount() { return requestCount; } public double getTotalDurationSec() { return totalDurationSec; } }
2. 分桶计算逻辑
假设你的原始数据是包含请求时间(毫秒)和请求时长(秒)的列表,按与EmpiricalDistribution一致的桶数进行分桶统计:
// 替换为你的原始请求数据集,Request需包含requestTimeMs和durationSec字段 List<Request> requests = ...; int bucketCount = ...; // 和之前EmpiricalDistribution使用的桶数保持一致 // 确定时间范围 long minTimeMs = requests.stream().mapToLong(Request::getRequestTimeMs).min().orElse(0); long maxTimeMs = requests.stream().mapToLong(Request::getRequestTimeMs).max().orElse(0); long bucketIntervalMs = (maxTimeMs - minTimeMs) / bucketCount; // 初始化所有时间桶 List<BucketStats> buckets = new ArrayList<>(); for (int i = 0; i < bucketCount; i++) { long startTime = minTimeMs + i * bucketIntervalMs; long endTime = startTime + bucketIntervalMs; // 修正最后一个桶的结束时间,避免超出最大时间 if (i == bucketCount - 1) { endTime = maxTimeMs; } buckets.add(new BucketStats(startTime, endTime)); } // 将请求分配到对应桶并统计 for (Request req : requests) { long reqTimeMs = req.getRequestTimeMs(); int bucketIndex = (int) ((reqTimeMs - minTimeMs) / bucketIntervalMs); // 处理边界请求,确保索引不越界 bucketIndex = Math.min(bucketIndex, bucketCount - 1); buckets.get(bucketIndex).recordRequest(req.getDurationSec()); } // 输出或存储每个时间桶的平均请求时长 buckets.stream() .filter(stat -> stat.getRequestCount() > 0) .forEach(stat -> { // 这里替换为你的存储逻辑 System.out.printf("时间区间: [%dms, %dms] | 平均请求时长: %.4fs | 请求数: %d%n", stat.getStartTimeMs(), stat.getEndTimeMs(), stat.getAverageDurationSec(), stat.getRequestCount()); // 若要严格对齐Prometheus的rate计算逻辑(验证用) double bucketIntervalSec = (stat.getEndTimeMs() - stat.getStartTimeMs()) / 1000.0; double rateSum = stat.getTotalDurationSec() / bucketIntervalSec; double rateCount = stat.getRequestCount() / bucketIntervalSec; double promStyleAvg = rateSum / rateCount; System.out.printf("Prometheus风格计算结果: %.4fs%n", promStyleAvg); });
3. 与原有EmpiricalDistribution逻辑的兼容
如果你想复用EmpiricalDistribution的分桶边界(避免重新计算时间区间),可以通过dist.getBinStats()获取每个桶的起止范围,再遍历请求数据分配到对应桶:
// 复用你已有的EmpiricalDistribution实例 EmpiricalDistribution dist = ...; List<BucketStats> buckets = new ArrayList<>(); // 从dist中获取桶的边界并初始化BucketStats for (SummaryStatistics stat : dist.getBinStats()) { // 注意:EmpiricalDistribution的bin是[start, end)区间,时间单位需和你的请求时间一致 long startTimeMs = (long) stat.getMin(); long endTimeMs = (long) stat.getMax(); buckets.add(new BucketStats(startTimeMs, endTimeMs)); } // 后续的请求分配、统计逻辑和上述一致
内容的提问来源于stack exchange,提问作者Borislav Stoilov
相关产品推荐
相关产品推荐

