PHP 指标异常检测

wen PHP项目 2

本文目录导读:

PHP 指标异常检测

  1. 基础异常检测类
  2. 实时监控系统实现
  3. 自适应阈值检测
  4. 配置和管理
  5. 使用示例
  6. 数据库表结构

我来为您提供一个完整的PHP指标异常检测方案,包含多种检测算法和实现示例。

基础异常检测类

<?php
class AnomalyDetector
{
    private array $data;
    private float $threshold;
    public function __construct(array $data, float $threshold = 3.0)
    {
        $this->data = $data;
        $this->threshold = $threshold;
    }
    /**
     * 基于标准差的异常检测(Z-Score)
     */
    public function zScoreDetection(): array
    {
        $mean = $this->calculateMean();
        $stdDev = $this->calculateStdDev($mean);
        if ($stdDev == 0) {
            return [];
        }
        $anomalies = [];
        foreach ($this->data as $index => $value) {
            $zScore = abs(($value - $mean) / $stdDev);
            if ($zScore > $this->threshold) {
                $anomalies[$index] = [
                    'value' => $value,
                    'z_score' => $zScore,
                    'mean' => $mean,
                    'std_dev' => $stdDev
                ];
            }
        }
        return $anomalies;
    }
    /**
     * 基于IQR(四分位距)的异常检测
     */
    public function iqrDetection(): array
    {
        $sortedData = $this->data;
        sort($sortedData);
        $q1 = $this->calculatePercentile($sortedData, 25);
        $q3 = $this->calculatePercentile($sortedData, 75);
        $iqr = $q3 - $q1;
        $lowerBound = $q1 - 1.5 * $iqr;
        $upperBound = $q3 + 1.5 * $iqr;
        $anomalies = [];
        foreach ($this->data as $index => $value) {
            if ($value < $lowerBound || $value > $upperBound) {
                $anomalies[$index] = [
                    'value' => $value,
                    'bounds' => [
                        'lower' => $lowerBound,
                        'upper' => $upperBound
                    ]
                ];
            }
        }
        return $anomalies;
    }
    /**
     * 基于移动平均线的异常检测
     */
    public function movingAverageDetection(int $windowSize = 5): array
    {
        $anomalies = [];
        $count = count($this->data);
        for ($i = $windowSize; $i < $count; $i++) {
            $window = array_slice($this->data, $i - $windowSize, $windowSize);
            $mean = array_sum($window) / $windowSize;
            $stdDev = $this->calculateStdDev($mean, $window);
            $currentValue = $this->data[$i];
            $deviation = abs($currentValue - $mean);
            if ($stdDev > 0 && ($deviation / $stdDev) > $this->threshold) {
                $anomalies[$i] = [
                    'value' => $currentValue,
                    'mean' => $mean,
                    'std_dev' => $stdDev,
                    'deviation' => $deviation
                ];
            }
        }
        return $anomalies;
    }
    /**
     * 季节性和趋势检测(STL分解)
     */
    public function seasonalDetection(int $seasonLength = 24): array
    {
        $seasonal = [];
        $trend = [];
        $residual = [];
        $count = count($this->data);
        // 计算季节性成分
        for ($i = 0; $i < $seasonLength; $i++) {
            $seasonValues = [];
            for ($j = $i; $j < $count; $j += $seasonLength) {
                $seasonValues[] = $this->data[$j];
            }
            $seasonal[$i] = array_sum($seasonValues) / count($seasonValues);
        }
        // 计算趋势和残差
        for ($i = 0; $i < $count; $i++) {
            $seasonalComponent = $seasonal[$i % $seasonLength];
            // 简单趋势检测
            if ($i > 0) {
                $trend[$i] = $this->data[$i] - $this->data[$i-1];
            } else {
                $trend[$i] = 0;
            }
            $residual[$i] = $this->data[$i] - $seasonalComponent - $trend[$i];
        }
        // 检测残差的异常
        $residualMean = array_sum($residual) / $count;
        $residualStdDev = $this->calculateStdDev($residualMean, $residual);
        $anomalies = [];
        foreach ($residual as $index => $value) {
            if ($residualStdDev > 0 && abs($value - $residualMean) > $this->threshold * $residualStdDev) {
                $anomalies[$index] = [
                    'value' => $this->data[$index],
                    'residual' => $value,
                    'seasonal' => $seasonal[$index % $seasonLength],
                    'trend' => $trend[$index]
                ];
            }
        }
        return $anomalies;
    }
    /**
     * 基于机器学习的检测(简单阈值)
     */
    public function mlBasedDetection(): array
    {
        // 使用K-means聚类简化版
        $clusters = $this->simpleKMeans(2);
        $anomalies = [];
        foreach ($clusters as $clusterId => $clusterData) {
            $clusterMean = array_sum($clusterData) / count($clusterData);
            $clusterStdDev = $this->calculateStdDev($clusterMean, $clusterData);
            foreach ($clusterData as $index => $value) {
                $zScore = abs($value - $clusterMean) / ($clusterStdDev ?: 1);
                if ($zScore > $this->threshold) {
                    $anomalies[$index] = [
                        'value' => $value,
                        'cluster' => $clusterId,
                        'z_score' => $zScore
                    ];
                }
            }
        }
        return $anomalies;
    }
    /**
     * 综合检测(多种算法结合)
     */
    public function compositeDetection(): array
    {
        $zScoreAnomalies = $this->zScoreDetection();
        $iqrAnomalies = $this->iqrDetection();
        $movingAnomalies = $this->movingAverageDetection();
        // 计算综合评分
        $composite = [];
        foreach ($this->data as $index => $value) {
            $score = 0;
            $methods = [];
            if (isset($zScoreAnomalies[$index])) {
                $score += 2;
                $methods[] = 'Z-Score';
            }
            if (isset($iqrAnomalies[$index])) {
                $score += 1;
                $methods[] = 'IQR';
            }
            if (isset($movingAnomalies[$index])) {
                $score += 2;
                $methods[] = 'Moving Average';
            }
            if ($score >= 3) { // 至少需要两种方法确认
                $composite[$index] = [
                    'value' => $value,
                    'score' => $score,
                    'methods' => $methods,
                    'timestamp' => time()
                ];
            }
        }
        return $composite;
    }
    /**
     * 实时监控类
     */
    public function realTimeDetection($newValue, $windowSize = 100): bool
    {
        // 滑动窗口更新
        array_push($this->data, $newValue);
        if (count($this->data) > $windowSize) {
            array_shift($this->data);
        }
        $zScoreAnomalies = $this->zScoreDetection();
        $lastIndex = count($this->data) - 1;
        return isset($zScoreAnomalies[$lastIndex]);
    }
    // 辅助方法
    private function calculateMean(): float
    {
        return array_sum($this->data) / count($this->data);
    }
    private function calculateStdDev($mean, $data = null): float
    {
        $data = $data ?: $this->data;
        $variance = 0;
        $count = count($data);
        foreach ($data as $value) {
            $variance += pow($value - $mean, 2);
        }
        return sqrt($variance / ($count - 1));
    }
    private function calculatePercentile(array $sortedData, float $percentile): float
    {
        $index = ($percentile / 100) * (count($sortedData) - 1);
        $floor = floor($index);
        $ceil = ceil($index);
        if ($floor == $ceil) {
            return $sortedData[$index];
        }
        $valueAtFloor = $sortedData[$floor];
        $valueAtCeil = $sortedData[$ceil];
        return $valueAtFloor + ($index - $floor) * ($valueAtCeil - $valueAtFloor);
    }
    private function simpleKMeans(int $k): array
    {
        // 简化版K-means实现
        $centroids = array_slice($this->data, 0, $k);
        $clusters = [];
        for ($iterations = 0; $iterations < 10; $iterations++) {
            $clusters = array_fill(0, $k, []);
            foreach ($this->data as $index => $value) {
                $nearestCentroid = 0;
                $minDistance = PHP_FLOAT_MAX;
                foreach ($centroids as $clusterId => $centroid) {
                    $distance = abs($value - $centroid);
                    if ($distance < $minDistance) {
                        $minDistance = $distance;
                        $nearestCentroid = $clusterId;
                    }
                }
                $clusters[$nearestCentroid][$index] = $value;
            }
            // 更新质心
            foreach ($clusters as $clusterId => $clusterData) {
                if (!empty($clusterData)) {
                    $centroids[$clusterId] = array_sum($clusterData) / count($clusterData);
                }
            }
        }
        return $clusters;
    }
}

实时监控系统实现

<?php
class MetricsMonitor
{
    private PDO $db;
    private Redis $redis;
    private array $config;
    public function __construct(PDO $db, Redis $redis, array $config = [])
    {
        $this->db = $db;
        $this->redis = $redis;
        $this->config = array_merge([
            'retention_days' => 30,
            'alert_threshold' => 3.0,
            'check_interval' => 60,
            'enabled_methods' => ['zscore', 'iqr', 'moving']
        ], $config);
    }
    /**
     * 实时监控入口
     */
    public function monitor(string $metricKey, $value): void
    {
        // 记录指标
        $this->recordMetric($metricKey, $value);
        // 获取历史数据
        $historicalData = $this->getHistoricalData($metricKey, 1000);
        if (count($historicalData) < 30) {
            return; // 数据量不足
        }
        // 执行异常检测
        $detector = new AnomalyDetector($historicalData, $this->config['alert_threshold']);
        $anomalies = $detector->compositeDetection();
        if (!empty($anomalies)) {
            $this->triggerAlert($metricKey, $value, $anomalies);
        }
        // 缓存最新状态
        $this->cacheStatus($metricKey, $value);
    }
    /**
     * 记录指标到数据库
     */
    private function recordMetric(string $metricKey, $value): void
    {
        $stmt = $this->db->prepare(
            "INSERT INTO metrics (metric_key, value, created_at) VALUES (?, ?, NOW())"
        );
        $stmt->execute([$metricKey, $value]);
        // 清理过期数据
        $this->cleanupOldData();
    }
    /**
     * 获取历史数据
     */
    private function getHistoricalData(string $metricKey, int $limit = 1000): array
    {
        $cacheKey = "metrics:{$metricKey}";
        $cached = $this->redis->get($cacheKey);
        if ($cached) {
            return json_decode($cached, true);
        }
        $stmt = $this->db->prepare(
            "SELECT value FROM metrics WHERE metric_key = ? ORDER BY created_at DESC LIMIT ?"
        );
        $stmt->execute([$metricKey, $limit]);
        $data = $stmt->fetchAll(PDO::FETCH_COLUMN);
        $data = array_reverse($data);
        // 缓存5分钟
        $this->redis->setex($cacheKey, 300, json_encode($data));
        return $data;
    }
    /**
     * 触发告警
     */
    private function triggerAlert(string $metricKey, $value, array $anomalies): void
    {
        $alertData = [
            'metric' => $metricKey,
            'value' => $value,
            'anomalies' => $anomalies,
            'timestamp' => time()
        ];
        // 发送到告警队列
        $this->redis->lpush('alerts:queue', json_encode($alertData));
        // 记录日志
        error_log("[METRIC ALERT] {$metricKey}: " . json_encode($anomalies));
    }
    /**
     * 缓存指标状态
     */
    private function cacheStatus(string $metricKey, $value): void
    {
        $statusKey = "status:{$metricKey}";
        $status = [
            'last_value' => $value,
            'updated_at' => time(),
            'count' => $this->redis->incr("count:{$metricKey}")
        ];
        $this->redis->setex($statusKey, 300, json_encode($status));
    }
    /**
     * 清理过期数据
     */
    private function cleanupOldData(): void
    {
        $retentionDate = date('Y-m-d H:i:s', strtotime("-{$this->config['retention_days']} days"));
        $stmt = $this->db->prepare("DELETE FROM metrics WHERE created_at < ?");
        $stmt->execute([$retentionDate]);
    }
    /**
     * 批量监控
     */
    public function batchMonitor(array $metrics): array
    {
        $results = [];
        foreach ($metrics as $key => $value) {
            $this->monitor($key, $value);
            $results[$key] = [
                'detected' => $this->checkAnomaly($key, $value),
                'timestamp' => time()
            ];
        }
        return $results;
    }
    /**
     * 检查单个指标是否异常
     */
    private function checkAnomaly(string $metricKey, $value): bool
    {
        $historicalData = $this->getHistoricalData($metricKey, 100);
        $detector = new AnomalyDetector($historicalData);
        return $detector->realTimeDetection($value);
    }
}

自适应阈值检测

<?php
class AdaptiveAnomalyDetector extends AnomalyDetector
{
    private array $weights;
    private float $learningRate;
    public function __construct(array $data, float $threshold = 3.0)
    {
        parent::__construct($data, $threshold);
        $this->learningRate = 0.1;
        $this->weights = array_fill(0, count($data), 1.0);
    }
    /**
     * 自适应EWMA(指数加权移动平均)检测
     */
    public function ewmaDetection(float $alpha = 0.3): array
    {
        $ewma = [];
        $ewmaVariance = [];
        $anomalies = [];
        if (empty($this->data)) {
            return [];
        }
        $ewma[0] = $this->data[0];
        $ewmaVariance[0] = pow($this->data[0] - $ewma[0], 2);
        for ($i = 1; $i < count($this->data); $i++) {
            // 更新EWMA
            $ewma[$i] = $alpha * $this->data[$i] + (1 - $alpha) * $ewma[$i-1];
            // 更新方差
            $ewmaVariance[$i] = $alpha * pow($this->data[$i] - $ewma[$i], 2) + 
                                (1 - $alpha) * $ewmaVariance[$i-1];
            $stdDev = sqrt($ewmaVariance[$i]);
            // 自适应阈值
            $adaptiveThreshold = $this->threshold;
            if ($stdDev < 0.1 * ($ewma[$i] + 1)) {
                $adaptiveThreshold += 0.5; // 波动小时提高阈值
            } elseif ($stdDev > $ewma[$i] * 0.5) {
                $adaptiveThreshold -= 0.5; // 波动大时降低阈值
            }
            if ($stdDev > 0 && abs($this->data[$i] - $ewma[$i]) > $adaptiveThreshold * $stdDev) {
                $anomalies[$i] = [
                    'value' => $this->data[$i],
                    'ewma' => $ewma[$i],
                    'std_dev' => $stdDev,
                    'adaptive_threshold' => $adaptiveThreshold,
                    'anomaly_score' => abs($this->data[$i] - $ewma[$i]) / ($stdDev + 0.001)
                ];
            }
        }
        return $anomalies;
    }
    /**
     * 基于梯度提升的检测
     */
    public function gradientDetection(): array
    {
        $gradients = [];
        $anomalies = [];
        // 计算梯度(变化率)
        for ($i = 1; $i < count($this->data); $i++) {
            $change = $this->data[$i] - $this->data[$i-1];
            $percentageChange = ($this->data[$i-1] != 0) ? 
                ($change / $this->data[$i-1]) : 0;
            $gradients[$i] = $percentageChange;
        }
        // 检测梯度异常
        $gradientMean = array_sum($gradients) / count($gradients);
        $gradientStdDev = $this->calculateStdDev($gradientMean, $gradients);
        foreach ($gradients as $index => $gradient) {
            $zScore = abs($gradient - $gradientMean) / ($gradientStdDev + 1e-10);
            if ($zScore > $this->threshold) {
                $anomalies[$index] = [
                    'value' => $this->data[$index],
                    'gradient' => $gradient,
                    'z_score' => $zScore,
                    'type' => $gradient > 0 ? 'spike' : 'drop'
                ];
            }
        }
        return $anomalies;
    }
    /**
     * 学习率自适应调整
     */
    public function adaptiveLearning(float $error): void
    {
        $this->learningRate *= (1 + $error);
        $this->learningRate = min(0.5, max(0.01, $this->learningRate));
    }
}

配置和管理

<?php
class AnomalyDetectionConfig
{
    private array $config;
    public function __construct(array $config = [])
    {
        $this->config = array_merge([
            'default_threshold' => 3.0,
            'detection_methods' => ['zscore', 'iqr', 'moving', 'ewma'],
            'data_retention' => 90,
            'alert_frequency' => 300,
            'max_alerts_per_hour' => 10,
            'min_data_points' => 30,
            'rolling_window' => 1000,
            'enable_ml' => true,
            'ml_model_path' => '/models/anomaly_detection.pkl'
        ], $config);
    }
    public function get(string $key, $default = null)
    {
        return $this->config[$key] ?? $default;
    }
    public function set(string $key, $value): void
    {
        $this->config[$key] = $value;
    }
    public function validate(): array
    {
        $errors = [];
        if ($this->config['default_threshold'] <= 0) {
            $errors[] = "Threshold must be positive";
        }
        if ($this->config['data_retention'] < 30) {
            $errors[] = "Data retention period too short";
        }
        if ($this->config['min_data_points'] < 10) {
            $errors[] = "Minimum data points too low for reliable detection";
        }
        return $errors;
    }
}

使用示例

<?php
// 基础使用
$data = [10, 12, 11, 13, 12, 11, 14, 15, 12, 100, 13, 12, 11, 14];
$detector = new AnomalyDetector($data, 2.5);
$anomalies = $detector->compositeDetection();
echo "基础异常检测结果:\n";
print_r($anomalies);
// 实时监控
$pdo = new PDO('mysql:host=localhost;dbname=monitoring', 'user', 'pass');
$redis = new Redis();
$redis->connect('localhost', 6379);
$monitor = new MetricsMonitor($pdo, $redis, [
    'threshold' => 3.0,
    'check_interval' => 60
]);
$monitor->monitor('cpu_usage', 95.5);
// 自适应检测
$adaptiveDetector = new AdaptiveAnomalyDetector($data, 3.0);
$ewmaAnomalies = $adaptiveDetector->ewmaDetection();
$gradientAnomalies = $adaptiveDetector->gradientDetection();
echo "EWMA检测结果:\n";
print_r($ewmaAnomalies);
echo "梯度检测结果:\n";
print_r($gradientAnomalies);

数据库表结构

-- 指标表
CREATE TABLE metrics (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    metric_key VARCHAR(255) NOT NULL,
    value DECIMAL(12,4) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_metric_key (metric_key),
    INDEX idx_created_at (created_at)
) ENGINE=InnoDB;
-- 告警表
CREATE TABLE alerts (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    metric_key VARCHAR(255) NOT NULL,
    severity ENUM('info', 'warning', 'critical') DEFAULT 'warning',
    description TEXT,
    data JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status ENUM('open', 'acknowledged', 'resolved') DEFAULT 'open',
    INDEX idx_metric_key (metric_key),
    INDEX idx_status (status)
) ENGINE=InnoDB;
-- 检测配置表
CREATE TABLE detection_configs (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    metric_key VARCHAR(255) NOT NULL UNIQUE,
    threshold DECIMAL(5,2) DEFAULT 3.00,
    methods JSON,
    window_size INT DEFAULT 1000,
    update_frequency INT DEFAULT 300,
    CONSTRAINT chk_threshold CHECK (threshold > 0)
) ENGINE=InnoDB;

这个方案提供了:

  1. 多种检测算法:Z-Score、IQR、移动平均、EWMA、梯度检测等
  2. 实时监控能力:实时检测和历史数据分析
  3. 自适应学习:根据数据特征动态调整阈值
  4. 完整的告警机制:告警记录、状态管理和通知
  5. 灵活配置:可按指标定制检测策略

您可以根据具体需求选择合适的检测方法或组合使用。

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