本文目录导读:

- 方案一:Crontab + 独立脚本(推荐)
- 方案二:使用Swoole/Workerman常驻进程
- 方案三:使用消息队列异步上报
- 方案四:使用专业的定时任务调度库
- 方案五:使用系统监控工具(推荐生产环境)
- 最佳实践建议
在PHP项目中实现定时推送监控数据点,通常有几种常见方案,以下是经过实践验证的几种方法,从简单到复杂排列:
Crontab + 独立脚本(推荐)
创建监控上报脚本
<?php
// /path/to/project/metrics/report.php
require_once __DIR__ . '/../vendor/autoload.php';
use App\Metrics\Collector;
use App\Metrics\Reporter;
// 收集指标
$metrics = [
'timestamp' => time(),
'host' => gethostname(),
'metrics' => [
'requests_per_minute' => getRequestCount(),
'error_rate' => getErrorRate(),
'average_response_time' => getAvgResponseTime(),
'active_users' => getActiveUsers(),
'memory_usage' => memory_get_usage(true),
'cpu_load' => sys_getloadavg()[0],
'disk_usage' => disk_free_space('/'),
'queue_size' => getQueueSize(),
]
];
// 推送到监控系统 (以Prometheus + PushGateway为例)
$reporter = new Reporter();
$reporter->pushToPushGateway($metrics);
// 或者推送到自建API
// $reporter->pushToApi($metrics, 'https://monitor.example.com/api/metrics');
echo "Metrics reported at " . date('Y-m-d H:i:s') . "\n";
设置Crontab
# 编辑crontab crontab -e # 每分钟执行一次 * * * * * /usr/bin/php /path/to/project/metrics/report.php >> /var/log/metrics_report.log 2>&1 # 每5分钟执行一次 */5 * * * * /usr/bin/php /path/to/project/metrics/report.php # 指定环境变量 * * * * * cd /path/to/project && /usr/bin/php artisan metrics:report
使用Laravel Task Scheduler(如果使用Laravel)
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
$schedule->command('metrics:report')
->everyMinute()
->withoutOverlapping()
->appendOutputTo(storage_path('logs/metrics.log'));
}
// app/Console/Commands/MetricsReport.php
class MetricsReport extends Command
{
protected $signature = 'metrics:report';
public function handle()
{
// 收集并上报指标
$this->collectAndReport();
}
}
使用Swoole/Workerman常驻进程
Swoole Timer示例
<?php
// metrics_server.php
use Swoole\Timer;
use Swoole\Process;
$worker = new Process(function() {
// 每5秒上报一次
Timer::tick(5000, function() {
$metrics = collectMetrics();
sendToMonitor($metrics);
});
});
$worker->start();
Workerman定时器
use Workerman\Worker;
use Workerman\Lib\Timer;
$worker = new Worker();
$worker->onWorkerStart = function() {
// 每30秒上报
Timer::add(30, function() {
$metrics = collectMetrics();
sendToMonitor($metrics);
});
};
Worker::runAll();
使用消息队列异步上报
生产者(业务代码中埋点)
<?php
// 在业务代码中收集指标并发送到队列
class MetricsCollector
{
public function record(string $metric, $value, array $tags = [])
{
$data = [
'metric' => $metric,
'value' => $value,
'tags' => $tags,
'timestamp' => microtime(true)
];
// 推送到Redis队列
Redis::lpush('metrics_queue', json_encode($data));
}
}
消费者(定时脚本处理队列)
<?php
// consumer.php - 通过supervisor或crontab运行
while (true) {
// 从队列批量获取
$batch = [];
for ($i = 0; $i < 100; $i++) {
$data = Redis::rpop('metrics_queue');
if ($data) {
$batch[] = json_decode($data, true);
} else {
break;
}
}
if (!empty($batch)) {
// 批量上报
batchReport($batch);
}
// 如果没有数据,休息一秒
if (count($batch) < 100) {
usleep(1000000);
}
}
使用专业的定时任务调度库
使用 Cron Expression 库
composer require mtdowling/cron-expression
// 自定义调度器
class Scheduler
{
private $tasks = [];
public function addTask(string $cronExpr, callable $callback)
{
$this->tasks[] = [
'expression' => Cron\CronExpression::factory($cronExpr),
'callback' => $callback
];
}
public function run()
{
while (true) {
$now = new DateTime();
foreach ($this->tasks as $task) {
if ($task['expression']->isDue($now)) {
call_user_func($task['callback']);
}
}
sleep(60); // 每分钟检查一次
}
}
}
// 使用
$scheduler = new Scheduler();
$scheduler->addTask('* * * * *', function() {
reportMetrics();
});
$scheduler->run();
使用系统监控工具(推荐生产环境)
Telegraf + InfluxDB + Grafana
# telegraf.conf
[[inputs.exec]]
commands = ["/usr/bin/php /path/to/metrics/script.php"]
interval = "30s"
data_format = "json"
json_name_key = "name"
json_time_key = "timestamp"
json_time_format = "unix"
# script.php输出JSON格式
echo json_encode([
"name" => "php_metrics",
"timestamp" => time(),
"fields" => [
"requests" => 1234,
"errors" => 5,
"response_time" => 0.235
],
"tags" => [
"host" => gethostname(),
"env" => "production"
]
]);
使用StatsD协议
// 使用 statsd-php 库
composer require league/statsd
$statsd = new League\StatsD\Client();
$statsd->configure([
'host' => 'localhost',
'port' => 8125,
'namespace' => 'app'
]);
// 在定时脚本中
$statsd->gauge('users.online', 123);
$statsd->increment('requests.count');
$statsd->timing('response.time', 234);
最佳实践建议
选择合适的推送策略
<?php
// 根据场景选择合适的推送方式
class MetricsPusher
{
const PUSH_INTERVAL = 60; // 默认60秒
// 批量推送(推荐)
public function batchPush()
{
$buffer = [];
// 收集一段时间内的指标
register_shutdown_function(function() use (&$buffer) {
if (!empty($buffer)) {
$this->sendToMonitor($buffer);
}
});
// 设置定时器定期推送
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGALRM, function() use (&$buffer) {
if (!empty($buffer)) {
$this->sendToMonitor($buffer);
$buffer = [];
}
});
pcntl_alarm(self::PUSH_INTERVAL);
}
}
}
异常处理与重试
<?php
class ReliablePusher
{
private $maxRetries = 3;
private $retryDelay = 2; // 秒
public function pushWithRetry(array $metrics)
{
for ($attempt = 1; $attempt <= $this->maxRetries; $attempt++) {
try {
$this->doPush($metrics);
return true;
} catch (\Exception $e) {
if ($attempt === $this->maxRetries) {
// 记录失败日志
Log::error("Metrics push failed after {$attempt} attempts", [
'error' => $e->getMessage(),
'metrics' => $metrics
]);
return false;
}
sleep($this->retryDelay * $attempt);
}
}
}
}
监控自身上报健康状态
<?php
// 监控上报服务本身
class MetricsHealthCheck
{
public function check()
{
$status = [
'last_report_time' => Cache::get('metrics_last_report_time'),
'report_count' => Cache::get('metrics_report_count', 0),
'error_count' => Cache::get('metrics_error_count', 0),
'uptime' => time() - Cache::get('metrics_start_time'),
'queue_size' => Redis::llen('metrics_queue')
];
// 如果超过5分钟没有上报,触发告警
if ($status['last_report_time'] < time() - 300) {
Alert::critical('Metrics reporting has stopped!');
}
return $status;
}
}
| 方案 | 适用场景 | 复杂度 | 可靠性 |
|---|---|---|---|
| Crontab | 大多数PHP项目 | 低 | 高 |
| Laravel Scheduler | Laravel项目 | 低 | 高 |
| Swoole/Workerman | 高并发常驻进程 | 中 | 高 |
| 消息队列 | 分布式系统 | 中高 | 非常高 |
| 系统监控工具 | 生产环境 | 中 | 非常高 |
推荐:对于大多数PHP项目,Crontab + 独立脚本 是最简单可靠的方案,如果使用框架(如Laravel),优先使用内置的Task Scheduler,对于高并发场景,考虑消息队列或常驻进程方案。