本文目录导读:

- 通用核心思路
- 方案一:数据库/日志 + 自定义后台(最常用)
- 方案二:Redis 实时监控(适合实时看板)
- 方案三:日志文件 + ELK / Grafana (生产级聚合)
- 方案四:分布式追踪(链路监控)
- 如何选择
在PHP项目中实现流程监控,通常指监控业务请求的处理过程、任务执行的状态(如队列任务、定时任务),或者长流程的流转(如订单审核、审批流),PHP 本身是“短生命周期”语言(请求结束进程销毁),所以监控思路和 Java 的长驻进程不同。
下面给出从简单到复杂的几种常用实现方案,以及对应场景。
通用核心思路
无论哪种方案,流程监控的本质都是:在关键节点(开始/结束/异常)记录时间、状态和数据,然后通过查询或实时推送呈现。
- 记录点:流程开始、每个关键步骤、流程结束、异常捕获。
- :
流程ID(如订单号)、步骤名称、时间、状态、附加数据(如用户ID、参数摘要)。 - 存储:文件、数据库、Redis、消息队列。
- 展示:自定义后台页面、日志系统、APM(应用性能监控)工具。
数据库/日志 + 自定义后台(最常用)
适合:对监控实时性要求不高,需要查看历史记录和统计的内部管理功能(如工单审批流程、订单处理流程)。
设计数据表(MySQL/PostgreSQL)
-- 流程实例表,每条记录代表一个流程 CREATE TABLE `process_instances` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `process_name` varchar(100) NOT NULL COMMENT '流程名称,如 order_audit', `biz_id` varchar(64) NOT NULL COMMENT '业务ID,如订单号', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0: 进行中, 1: 成功, 2: 失败, 3: 超时', `current_step` varchar(100) DEFAULT NULL COMMENT '当前所在步骤', `started_at` datetime DEFAULT NULL, `finished_at` datetime DEFAULT NULL, `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_biz` (`process_name`,`biz_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB; -- 流程流转日志表,记录每一步的日志 CREATE TABLE `process_logs` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `process_name` varchar(100) NOT NULL, `biz_id` varchar(64) NOT NULL, `step_name` varchar(100) NOT NULL COMMENT '步骤名,如 validate_order, call_payment_api', `status` varchar(20) NOT NULL COMMENT 'success / fail / running', `message` text COMMENT '日志详情或错误信息', `cost_ms` int unsigned DEFAULT '0' COMMENT '该步骤耗时(毫秒)', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, KEY `idx_biz` (`process_name`,`biz_id`), KEY `idx_time` (`created_at`) ) ENGINE=InnoDB;
PHP 代码埋点
<?php
class ProcessMonitor
{
private string $processName;
private string $bizId;
private array $stepTimes = []; // 用于计算耗时
public function __construct(string $processName, string $bizId)
{
$this->processName = $processName;
$this->bizId = $bizId;
// 可选:插入流程实例记录
DB::table('process_instances')->updateOrInsert(
['process_name' => $processName, 'biz_id' => $bizId],
['status' => 0, 'started_at' => now(), 'current_step' => 'init']
);
}
public function startStep(string $stepName): void
{
$this->stepTimes[$stepName] = microtime(true);
}
public function endStepSuccess(string $stepName, string $message = ''): void
{
$cost = $this->calcCost($stepName);
// 写入日志表
DB::table('process_logs')->insert([
'process_name' => $this->processName,
'biz_id' => $this->bizId,
'step_name' => $stepName,
'status' => 'success',
'message' => $message,
'cost_ms' => $cost,
'created_at' => now(),
]);
// 更新实例表当前步骤
DB::table('process_instances')
->where(['process_name' => $this->processName, 'biz_id' => $this->bizId])
->update(['current_step' => $stepName]);
}
public function endStepFail(string $stepName, string $error): void
{
$cost = $this->calcCost($stepName);
DB::table('process_logs')->insert([
'process_name' => $this->processName,
'biz_id' => $this->bizId,
'step_name' => $stepName,
'status' => 'fail',
'message' => $error,
'cost_ms' => $cost,
]);
// 标记整个流程失败
DB::table('process_instances')
->where(['process_name' => $this->processName, 'biz_id' => $this->bizId])
->update(['status' => 2, 'finished_at' => now()]);
}
public function finishProcess(): void
{
DB::table('process_instances')
->where(['process_name' => $this->processName, 'biz_id' => $this->bizId])
->update(['status' => 1, 'finished_at' => now()]);
}
private function calcCost(string $stepName): int
{
if (isset($this->stepTimes[$stepName])) {
return (int)((microtime(true) - $this->stepTimes[$stepName]) * 1000);
}
return 0;
}
}
// 使用示例:下单流程监控
$monitor = new ProcessMonitor('order_create', $orderNo);
$monitor->startStep('validate_param');
// ... 校验参数逻辑 ...
$monitor->endStepSuccess('validate_param');
$monitor->startStep('deduct_stock');
// ... 扣库存逻辑 ...
$monitor->endStepSuccess('deduct_stock');
// 如果扣库存失败
// $monitor->endStepFail('deduct_stock', '库存不足');
$monitor->startStep('save_order');
// ... 保存订单 ...
$monitor->endStepSuccess('save_order');
$monitor->finishProcess(); // 标记整个流程成功
后台展示
写一个简单的管理后台,SQL 查询:SELECT * FROM process_logs WHERE process_name='order_create' AND biz_id='xxx' ORDER BY created_at,按步骤时间线展示。
Redis 实时监控(适合实时看板)
适合:需要实时掌握当前正在执行的任务数量、吞吐量、耗时分布(如数据采集管道、异步任务处理器)。
不使用数据库(写磁盘慢),而是使用 Redis 的 List、Hash、Sorted Set 结构。
class RealTimeMonitor
{
private Redis $redis;
private string $prefix = 'flow:';
// 记录当前活跃流程数
public function incrActive(string $processName): void
{
$this->redis->incr($this->prefix . 'active:' . $processName);
}
public function decrActive(string $processName): void
{
$this->redis->decr($this->prefix . 'active:' . $processName);
}
// 记录耗时到有序集合(用于计算 P50/P99)
public function recordLatency(string $processName, int $milliseconds): void
{
$this->redis->zAdd($this->prefix . 'latency:' . $processName, $milliseconds, uniqid());
// 可选:设置过期时间,只保留最近1小时的数据
$this->redis->expire($this->prefix . 'latency:' . $processName, 3600);
}
// 获取当前活跃数
public function getActiveCount(string $processName): int
{
return (int)$this->redis->get($this->prefix . 'active:' . $processName);
}
// 获取平均耗时
public function getAvgLatency(string $processName): float
{
$count = $this->redis->zCard($this->prefix . 'latency:' . $processName);
if ($count == 0) return 0;
$sum = 0;
// 注意:大数据量下不要这样做,仅示例,实际应使用 Redis 聚合或后台计算
$all = $this->redis->zRange($this->prefix . 'latency:' . $processName, 0, -1, true);
return array_sum($all) / $count;
}
// 记录日志到 List(用于看最新N条)
public function logEvent(string $processName, string $bizId, string $message): void
{
$this->redis->lPush($this->prefix . 'log:' . $processName, json_encode([
'biz_id' => $bizId, 'msg' => $message, 'time' => now()
]));
$this->redis->lTrim($this->prefix . 'log:' . $processName, 0, 99); // 只保留最近100条
}
}
前端看板:通过 API 读取上述 Redis 数据(GET /api/monitor?process=order),前端轮询展示卡片、折线图等。
日志文件 + ELK / Grafana (生产级聚合)
适合:微服务架构、大量服务器,需要集中式分析和告警,这是很多大厂的方式。
-
PHP 日志输出:使用 Monolog 或自定义 Logger,按固定 JSON 格式输出到文件。
// 在流程关键点 $logger->info('process.step', [ 'process' => 'order_audit', 'biz_id' => $orderId, 'step' => 'call_third_party', 'status' => 'success', 'duration_ms' => 123, 'extra' => ['api_name' => 'risk_verify'] ]); -
日志收集:Filebeat / Fluentd 读取日志文件,发送到 Elasticsearch。
-
展示:Kibana 创建仪表板,可以按
process聚合,查看avg(duration_ms)、count per minute、error rate,甚至可以设置告警(当duration_ms > 500发邮件)。 -
替代方案:如果不想引入 ELK 那么重,可以用 Grafana + Loki(轻量日志聚合)或者 Datadog、Sentry 等 SaaS 服务(Sentry 可以监控错误和性能)。
分布式追踪(链路监控)
如果流程跨越多个微服务(PHP 调用 Java 服务,再调用 PHP 异步任务),上述单进程监控就不够了,需要实现分布式追踪。
- 标准:OpenTelemetry(推荐)、Zipkin、Jaeger。
- PHP 实现:使用
opentelemetry/opentelemetry-php包,在 HTTP 请求入口、RPC 调用、队列消息发送/接收处创建 Span(跨度)。 - 效果:可以在 Jaeger UI 上看到一个请求从
Nginx -> PHP-FPM -> MySQL -> RabbitMQ -> Worker的完整调用链路,每一步耗时一目了然。
// 使用 OpenTelemetry API 示例(伪代码)
$tracer = OpenTelemetry::getTracer('my-app');
$span = $tracer->spanBuilder('process.order')->startSpan();
$span->setAttribute('order_id', $orderId);
try {
// 步骤1
$childSpan = $tracer->spanBuilder('validate')->startSpan();
// ... 校验逻辑 ...
$childSpan->end();
// 步骤2
$childSpan2 = $tracer->spanBuilder('deduct_stock')->startSpan();
// ...
$childSpan2->end();
$span->end(); // 成功结束
} catch (\Throwable $e) {
$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
$span->end();
}
如何选择
| 场景 | 推荐方案 | 复杂度 |
|---|---|---|
| 内部管理后台,看单个订单的审核流程细节 | 数据库 + 自定义列表 | 低 |
| 实时运维大屏,看当前处理中的任务数和耗时 | Redis + API + 前端轮询 | 中 |
| 微服务/多服务器,历史数据分析和告警 | JSON 日志 + ELK / Loki / Grafana | 高 |
| 跨服务调用链跟踪,用于定位性能瓶颈 | OpenTelemetry + Jaeger | 高 |
建议先从简单的方案一开始,建两个表、写一个 Trait,很快就能看到效果,后续如果流量变大、需要实时性,再增加方案二或三。