PHP项目内存使用率超标如何及时预警

wen PHP项目 28

本文目录导读:

PHP项目内存使用率超标如何及时预警

  1. 应用层实时监控
  2. 系统层监控
  3. 日志告警方案
  4. 完善预警体系建议
  5. 最佳实践建议

针对PHP项目内存使用率超标预警,可以从应用层监控系统层监控日志告警三个维度来设计预警方案,以下是具体的实现思路和代码示例:

应用层实时监控

在PHP代码中嵌入内存检查逻辑,当内存使用超过阈值时主动告警。

1 核心监控函数

<?php
/**
 * 内存监控类
 */
class MemoryMonitor {
    private $threshold; // 阈值(单位:MB)
    private $alertCallback; // 告警回调函数
    public function __construct($thresholdMB = 128, callable $alertCallback = null) {
        $this->threshold = $thresholdMB * 1024 * 1024; // 转换为字节
        $this->alertCallback = $alertCallback;
    }
    /**
     * 检查当前内存使用
     * @return bool 是否超标
     */
    public function checkMemoryUsage() {
        $usage = memory_get_usage(true); // 获取实际分配的内存
        if ($usage > $this->threshold) {
            $this->triggerAlert($usage);
            return true;
        }
        return false;
    }
    /**
     * 触发告警
     */
    private function triggerAlert($usage) {
        $message = sprintf(
            "[MEMORY ALERT] 内存使用超标: %.2f MB (阈值: %.2f MB)\n",
            $usage / 1024 / 1024,
            $this->threshold / 1024 / 1024
        );
        // 1. 记录日志
        error_log($message);
        // 2. 调用自定义告警回调
        if ($this->alertCallback) {
            call_user_func($this->alertCallback, $usage, $this->threshold);
        }
        // 3. 发送通知(示例:邮件/钉钉/企业微信)
        $this->sendNotification($message);
    }
    /**
     * 发送告警通知(以钉钉Webhook为例)
     */
    private function sendNotification($message) {
        $webhookUrl = 'https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN';
        $data = [
            'msgtype' => 'text',
            'text' => [
                'content' => $message
            ]
        ];
        $ch = curl_init($webhookUrl);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_exec($ch);
        curl_close($ch);
    }
}
// 使用示例
$monitor = new MemoryMonitor(256, function($usage, $threshold) {
    // 自定义告警逻辑:例如写入Redis、更新数据库
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis->lPush('memory_alerts', json_encode([
        'time' => date('Y-m-d H:i:s'),
        'usage' => $usage,
        'threshold' => $threshold
    ]));
});
// 在关键代码段执行内存检查
$monitor->checkMemoryUsage();

2 自动监控中间件(适用于框架)

以Laravel为例,创建中间件自动监控每个请求的内存使用:

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class MemoryMonitorMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);
        // 请求结束后检查内存
        $peakMemory = memory_get_peak_usage(true);
        $threshold = 128 * 1024 * 1024; // 128MB
        if ($peakMemory > $threshold) {
            \Log::warning('请求内存超标', [
                'url' => $request->fullUrl(),
                'memory' => $peakMemory / 1024 / 1024 . 'MB',
                'method' => $request->method()
            ]);
            // 发送告警
            // ...
        }
        return $response;
    }
}

系统层监控

1 使用shell脚本监控所有PHP进程

#!/bin/bash
# php_memory_monitor.sh
# 配置
THRESHOLD_MB=256
LOG_FILE="/var/log/php_memory_alerts.log"
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
# 获取所有PHP进程的内存使用
php_pids=$(pgrep -f "php-fpm|php-cgi|php-cli")
for pid in $php_pids; do
    if [ -d "/proc/$pid" ]; then
        # 获取内存使用(单位:KB)
        memory_kb=$(awk '/VmRSS/{print $2}' /proc/$pid/status 2>/dev/null)
        if [ ! -z "$memory_kb" ]; then
            memory_mb=$((memory_kb / 1024))
            if [ $memory_mb -gt $THRESHOLD_MB ]; then
                # 获取进程详细信息
                cmdline=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')
                message="[MEMORY ALERT] PID: $pid, 内存: ${memory_mb}MB, 命令: $cmdline"
                # 记录日志
                echo "$(date '+%Y-%m-%d %H:%M:%S') $message" >> $LOG_FILE
                # 发送告警
                curl -s -X POST -H "Content-Type: application/json" \
                    -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$message\"}}" \
                    $WEBHOOK_URL &
            fi
        fi
    fi
done

设置cron定时任务(每分钟检查):

* * * * * /usr/local/bin/php_memory_monitor.sh

2 使用Prometheus + Grafana监控

创建自定义exporter暴露PHP内存数据:

<?php
// metrics.php - 供Prometheus抓取的指标
header('Content-Type: text/plain; charset=utf-8');
$metrics = [];
// 当前内存使用
$currentMemory = memory_get_usage(true);
$peakMemory = memory_get_peak_usage(true);
$metrics[] = "# HELP php_memory_bytes Current PHP memory usage in bytes";
$metrics[] = "# TYPE php_memory_bytes gauge";
$metrics[] = "php_memory_bytes{type=\"current\"} $currentMemory";
$metrics[] = "php_memory_bytes{type=\"peak\"} $peakMemory";
// 输出指标
echo implode("\n", $metrics) . "\n";

Prometheus告警规则:

groups:
- name: php_memory_alerts
  rules:
  - alert: PHPHighMemoryUsage
    expr: php_memory_bytes{type="current"} > 256000000
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "PHP内存使用超标"
      description: "当前内存使用 {{ $value | humanize }},超过阈值 256MB"

日志告警方案

1 使用ELK+Watcher进行日志告警

  1. 在PHP中记录内存告警日志:
    <?php
    // 日志格式:JSON格式便于解析
    $logData = [
     'type' => 'memory_alert',
     'timestamp' => date('Y-m-d\TH:i:s\Z'),
     'memory_usage' => memory_get_usage(true),
     'peak_memory' => memory_get_peak_usage(true),
     'script' => $_SERVER['SCRIPT_NAME'] ?? 'unknown',
     'pid' => getmypid()
    ];

error_log(json_encode($logData) . "\n", 3, '/var/log/php_memory.json.log');


2. 配置Logstash解析日志,发送到Elasticsearch
3. 在Kibana中创建Watcher告警:
```json
{
  "trigger": {
    "schedule": {
      "interval": "1m"
    }
  },
  "input": {
    "search": {
      "request": {
        "indices": ["php-logs-*"],
        "body": {
          "query": {
            "bool": {
              "filter": [
                {"term": {"type": "memory_alert"}},
                {"range": {"memory_usage": {"gte": 268435456}}}
              ]
            }
          }
        }
      }
    }
  },
  "actions": {
    "email_admin": {
      "email": {
        "to": "admin@example.com",
        "subject": "PHP内存超标告警",
        "body": "发现 {{ctx.payload.hits.total}} 条内存超标记录"
      }
    }
  }
}

完善预警体系建议

1 多级阈值设置

级别 阈值 处理方式
WARNING 80%阈值 仅记录日志
CRITICAL 100%阈值 发送告警通知
EMERGENCY 150%阈值 自动重启进程

2 告警渠道整合

<?php
class AlertManager {
    private $channels = [];
    public function addChannel($name, callable $handler) {
        $this->channels[$name] = $handler;
    }
    public function sendAlert($level, $message, $context = []) {
        foreach ($this->channels as $name => $handler) {
            try {
                $handler($level, $message, $context);
            } catch (\Exception $e) {
                error_log("告警渠道 {$name} 发送失败: " . $e->getMessage());
            }
        }
    }
}
// 使用示例
$alertManager = new AlertManager();
$alertManager->addChannel('email', function($level, $msg, $ctx) {
    // 发送邮件
});
$alertManager->addChannel('wechat', function($level, $msg, $ctx) {
    // 发送企业微信
});
$alertManager->addChannel('sms', function($level, $msg, $ctx) {
    // 发送短信(仅紧急级别)
    if ($level === 'EMERGENCY') {
        // 发送短信
    }
});
$alertManager->sendAlert('CRITICAL', '内存使用超标', [
    'memory' => '300MB',
    'threshold' => '256MB',
    'pid' => 12345,
    'script' => '/path/to/script.php'
]);

3 告警抑制机制

避免重复告警骚扰:

<?php
class AlertSuppressor {
    private $redis;
    private $cooldown = 300; // 5分钟冷却时间
    public function shouldSend($alertKey) {
        $key = "alert_suppress:{$alertKey}";
        if ($this->redis->exists($key)) {
            return false; // 冷却期内不发送
        }
        // 设置冷却期
        $this->redis->setex($key, $this->cooldown, 1);
        return true;
    }
}

最佳实践建议

  1. 选择适合的监控粒度

    • 线上环境:每分钟检查一次
    • 重要业务接口:每次请求都检查
    • 批量处理脚本:每处理N条数据检查一次
  2. 告警分级

    • P0级别:内存使用>90%阈值,立即电话通知
    • P1级别:>80%阈值,发送即时消息
    • P2级别:>70%阈值,记录日志
  3. 结合性能分析

    • 内存超标时自动生成内存快照(memory_get_usage配合debug_backtrace)
    • 使用Xdebug profiling分析内存泄漏
  4. 自动化处理

    • 自动重启高内存进程
    • 自动扩展资源(如增加FPM进程数)

通过以上方案组合,可以实现对PHP项目内存使用的全方位监控预警,确保问题能够及时发现和处理。

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