Java动态基线案例

wen java案例 3

本文目录导读:

Java动态基线案例

  1. 什么是动态基线
  2. 案例1:API响应时间动态基线检测
  3. 案例2:多维度动态基线检测
  4. 案例3:自适应加权移动平均基线
  5. 最佳实践建议

我来详细介绍Java动态基线的概念和实际案例。

什么是动态基线

动态基线是一种实时检测系统性能或行为异常的方法,通过持续学习历史数据,自动调整正常行为的阈值范围,而非使用固定阈值。

案例1:API响应时间动态基线检测

核心实现

import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class DynamicBaselineDetector {
    // 存储每个API的时间序列数据
    private final Map<String, SlidingWindow> metricsMap = new ConcurrentHashMap<>();
    // 基线配置
    private final BaselineConfig config;
    public DynamicBaselineDetector(BaselineConfig config) {
        this.config = config;
        startBaselineUpdateTask();
    }
    // 记录指标
    public void recordMetric(String apiName, double value) {
        metricsMap.computeIfAbsent(apiName, k -> 
            new SlidingWindow(config.getWindowSize(), config.getWindowUnit()))
            .add(value);
    }
    // 检测异常
    public AnomalyResult detectAnomaly(String apiName, double currentValue) {
        SlidingWindow window = metricsMap.get(apiName);
        if (window == null || window.size() < config.getMinSamples()) {
            return new AnomalyResult(false, 0, 0, 0);
        }
        double mean = window.getMean();
        double stdDev = window.getStdDev();
        double threshold = config.getThresholdMultiplier() * stdDev;
        boolean isAnomaly = Math.abs(currentValue - mean) > threshold;
        return new AnomalyResult(
            isAnomaly, 
            currentValue, 
            mean, 
            mean + threshold
        );
    }
    // 定期更新基线
    private void startBaselineUpdateTask() {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            metricsMap.values().forEach(SlidingWindow::pruneOldData);
        }, 1, 1, TimeUnit.HOURS);
    }
    // 滑动窗口实现
    static class SlidingWindow {
        private final Deque<TimestampedValue> data = new LinkedList<>();
        private final long windowSize;
        private final TimeUnit unit;
        public SlidingWindow(long size, TimeUnit unit) {
            this.windowSize = size;
            this.unit = unit;
        }
        public synchronized void add(double value) {
            data.addLast(new TimestampedValue(value, Instant.now()));
            pruneOldData();
        }
        public synchronized void pruneOldData() {
            Instant cutoff = Instant.now().minus(windowSize, unit.toChronoUnit());
            while (!data.isEmpty() && data.getFirst().timestamp.isBefore(cutoff)) {
                data.removeFirst();
            }
        }
        public synchronized double getMean() {
            if (data.isEmpty()) return 0;
            return data.stream()
                .mapToDouble(tv -> tv.value)
                .average()
                .orElse(0);
        }
        public synchronized double getStdDev() {
            if (data.size() < 2) return 0;
            double mean = getMean();
            double variance = data.stream()
                .mapToDouble(tv -> Math.pow(tv.value - mean, 2))
                .average()
                .orElse(0);
            return Math.sqrt(variance);
        }
        public synchronized int size() {
            return data.size();
        }
        static class TimestampedValue {
            final double value;
            final Instant timestamp;
            TimestampedValue(double value, Instant timestamp) {
                this.value = value;
                this.timestamp = timestamp;
            }
        }
    }
    // 配置类
    static class BaselineConfig {
        private long windowSize = 24;
        private TimeUnit windowUnit = TimeUnit.HOURS;
        private int minSamples = 100;
        private double thresholdMultiplier = 3.0;
        // getters and setters
        public long getWindowSize() { return windowSize; }
        public void setWindowSize(long windowSize) { this.windowSize = windowSize; }
        public TimeUnit getWindowUnit() { return windowUnit; }
        public void setWindowUnit(TimeUnit windowUnit) { this.windowUnit = windowUnit; }
        public int getMinSamples() { return minSamples; }
        public void setMinSamples(int minSamples) { this.minSamples = minSamples; }
        public double getThresholdMultiplier() { return thresholdMultiplier; }
        public void setThresholdMultiplier(double thresholdMultiplier) { 
            this.thresholdMultiplier = thresholdMultiplier; 
        }
    }
    // 异常结果
    static class AnomalyResult {
        private final boolean isAnomaly;
        private final double currentValue;
        private final double baseline;
        private final double threshold;
        public AnomalyResult(boolean isAnomaly, double currentValue, 
                           double baseline, double threshold) {
            this.isAnomaly = isAnomaly;
            this.currentValue = currentValue;
            this.baseline = baseline;
            this.threshold = threshold;
        }
        @Override
        public String toString() {
            if (isAnomaly) {
                return String.format(
                    "⚠️ 异常检测: 当前值=%.2f, 基线=%.2f, 阈值=%.2f",
                    currentValue, baseline, threshold
                );
            }
            return String.format(
                "✅ 正常: 当前值=%.2f, 基线=%.2f", 
                currentValue, baseline
            );
        }
    }
}

使用示例

public class DynamicBaselineDemo {
    public static void main(String[] args) throws InterruptedException {
        // 配置动态基线
        DynamicBaselineDetector.BaselineConfig config = 
            new DynamicBaselineDetector.BaselineConfig();
        config.setWindowSize(1);
        config.setWindowUnit(TimeUnit.HOURS);
        config.setMinSamples(10);
        config.setThresholdMultiplier(2.0);
        DynamicBaselineDetector detector = new DynamicBaselineDetector(config);
        // 模拟正常数据
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            // 正常响应时间在 100-200ms 之间
            double normalLatency = 150 + random.nextGaussian() * 20;
            detector.recordMetric("/api/users", normalLatency);
            Thread.sleep(10);
        }
        // 检测异常
        double anomalousLatency = 500; // 异常值
        DynamicBaselineDetector.AnomalyResult result = 
            detector.detectAnomaly("/api/users", anomalousLatency);
        System.out.println(result);
        // 正常值检测
        double normalLatency = 160;
        result = detector.detectAnomaly("/api/users", normalLatency);
        System.out.println(result);
    }
}

案例2:多维度动态基线检测

import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.util.*;
public class MultiDimensionBaseline {
    // 存储多维度的历史数据
    private final Map<String, Map<String, DynamicBaselineDetector.SlidingWindow>> 
        dimensionMetrics = new HashMap<>();
    // 记录带维度的指标
    public void recordMetric(String metricName, Map<String, String> dimensions, double value) {
        String dimensionKey = buildDimensionKey(dimensions);
        dimensionMetrics.computeIfAbsent(metricName, k -> new HashMap<>())
            .computeIfAbsent(dimensionKey, k -> 
                new DynamicBaselineDetector.SlidingWindow(24, TimeUnit.HOURS))
            .add(value);
    }
    // 检测异常(考虑时间维度)
    public AnomalyAnalysis detectWithTimePattern(String metricName, 
                                                 Map<String, String> dimensions, 
                                                 double currentValue) {
        dimensions.put("hour_of_day", String.valueOf(LocalDateTime.now().getHour()));
        dimensions.put("day_of_week", 
            String.valueOf(LocalDateTime.now().getDayOfWeek().getValue()));
        String dimensionKey = buildDimensionKey(dimensions);
        String timePatternKey = buildTimePatternKey(dimensions);
        // 获取同期历史数据
        DynamicBaselineDetector.SlidingWindow timeWindow = 
            dimensionMetrics.getOrDefault(metricName, new HashMap<>())
                .get(timePatternKey);
        if (timeWindow != null && timeWindow.size() > 10) {
            double mean = timeWindow.getMean();
            double stdDev = timeWindow.getStdDev();
            double threshold = 2.5 * stdDev;
            return new AnomalyAnalysis(
                Math.abs(currentValue - mean) > threshold,
                currentValue,
                mean,
                threshold
            );
        }
        // 使用整体基线
        DynamicBaselineDetector.SlidingWindow overallWindow = 
            dimensionMetrics.getOrDefault(metricName, new HashMap<>())
                .get("overall");
        if (overallWindow != null) {
            double mean = overallWindow.getMean();
            double stdDev = overallWindow.getStdDev();
            double threshold = 3.0 * stdDev;
            return new AnomalyAnalysis(
                Math.abs(currentValue - mean) > threshold,
                currentValue,
                mean,
                threshold
            );
        }
        return new AnomalyAnalysis(false, currentValue, 0, 0);
    }
    private String buildDimensionKey(Map<String, String> dimensions) {
        return dimensions.entrySet().stream()
            .sorted(Map.Entry.comparingByKey())
            .map(e -> e.getKey() + "=" + e.getValue())
            .reduce((a, b) -> a + "&" + b)
            .orElse("");
    }
    private String buildTimePatternKey(Map<String, String> dimensions) {
        return "hour=" + dimensions.get("hour_of_day") + 
               "&day=" + dimensions.get("day_of_week");
    }
    static class AnomalyAnalysis {
        private final boolean isAnomaly;
        private final double currentValue;
        private final double baseline;
        private final double threshold;
        public AnomalyAnalysis(boolean isAnomaly, double currentValue, 
                             double baseline, double threshold) {
            this.isAnomaly = isAnomaly;
            this.currentValue = currentValue;
            this.baseline = baseline;
            this.threshold = threshold;
        }
        public boolean isAnomaly() { return isAnomaly; }
        public double getDeviation() { return (currentValue - baseline) / baseline * 100; }
    }
}

案例3:自适应加权移动平均基线

public class AdaptiveEWMABaseline {
    // 指数加权移动平均
    private double ewma = 0;
    private double ewmv = 0; // 方差
    private double alpha = 0.3; // 学习率
    // 自适应学习率调整
    private int sampleCount = 0;
    private final int warmupPeriod = 30;
    public void update(double value) {
        sampleCount++;
        if (sampleCount == 1) {
            ewma = value;
            ewmv = 0;
            return;
        }
        // 自适应调整学习率
        if (sampleCount <= warmupPeriod) {
            alpha = 1.0 / sampleCount;
        } else {
            alpha = 0.3;
        }
        // 更新均值
        double prevEwma = ewma;
        ewma = alpha * value + (1 - alpha) * ewma;
        // 更新方差
        ewmv = (1 - alpha) * ewmv + alpha * Math.pow(value - prevEwma, 2);
    }
    public boolean isAnomaly(double value) {
        if (sampleCount < warmupPeriod) return false;
        double stdDev = Math.sqrt(ewmv);
        double threshold = 2.5 * stdDev;
        return Math.abs(value - ewma) > threshold;
    }
    public double getBaseline() {
        return ewma;
    }
}

最佳实践建议

  1. 数据预处理:清洗异常值,处理缺失数据
  2. 多时间窗口:结合短窗口(15min)和长窗口(24h)
  3. 衰减因子:最近数据权重更高
  4. 业务规则:结合业务知识设定合理阈值
  5. 报警抑制:避免频繁报警,设置冷静期

这些案例展示了动态基线在Java中的实际应用,可以根据具体业务场景灵活调整。

抱歉,评论功能暂时关闭!