
本文介绍在 RoadRunner 高性能 PHP 应用服务器中,如何正确协同使用 libev(通过 ext-ev)与 HTTP 请求处理流程,实现在不阻塞 HTTP 响应的前提下,周期性更新全局状态(如缓存、计数器、监控指标等)。
本文介绍在 roadrunner 高性能 php 应用服务器中,如何正确协同使用 libev(通过 `ext-ev`)与 http 请求处理流程,实现在不阻塞 http 响应的前提下,周期性更新全局状态(如缓存、计数器、监控指标等)。
RoadRunner 是基于 Go 编写的高性能 PHP 应用服务器,采用“预加载 + 多路复用 Worker”模型,其核心特点是:每个 Worker 进程是单线程、同步阻塞式运行的。这意味着你无法像在 ReactPHP 或 Swoole 中那样,在一个事件循环中同时调度 HTTP 请求和定时器——RoadRunner 的 PSR7Worker::waitRequest() 是一个阻塞式系统调用,它会挂起当前 PHP 执行直到收到下一个请求。
因此,直接在 Worker 主循环外调用 Ev::run(Ev::RUN_NOWAIT)(如原代码所示)是无效的:该调用仅执行一次就立即返回,且不会持续驱动事件循环;而 Ev::run() 则会永久阻塞,导致根本无法进入 waitRequest(),HTTP 服务完全停滞。
✅ 正确解法:将 Ev 事件循环嵌入 RoadRunner Worker 的主循环中,并在每次请求处理间隙主动「步进」一次事件循环。这要求我们手动控制 Ev::run() 的执行节奏,使其与请求生命周期共存。
以下是可落地的实现方案(兼容原生 ext-ev,无需引入 ReactPHP):
use Spiral\RoadRunner;
use Nyholm\Psr7;
use Ev;
include "vendor/autoload.php";
$worker = RoadRunner\Worker::create();
$psrFactory = new Psr7\Factory\Psr17Factory();
$psr7 = new RoadRunner\Http\PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);
// 全局共享状态(注意:多 Worker 进程间不共享!)
$global_variable = 0;
// 创建周期性定时器(每 1 秒触发,首次延迟 2 秒)
$timer = new EvTimer(2.0, 1.0, function ($w) use (&$global_variable) {
$global_variable++;
echo "[Ev] Tick #{$global_variable} at " . date('H:i:s') . PHP_EOL;
// 可在此处执行轻量 DB 查询、API 调用、日志采样等
// ⚠️ 注意:避免耗时操作,否则会拖慢整个 Worker 响应
});
// 启动 Ev 事件循环(非阻塞模式:只处理就绪事件,不等待)
// 我们将在主循环中定期调用它
Ev::run(Ev::RUN_NOWAIT);
while (true) {
try {
// 【关键】在 waitRequest 前「让出」控制权给 Ev,处理已就绪的定时器/IO 事件
// 这确保了定时器能及时触发,且不阻塞请求接收
Ev::run(Ev::RUN_NOWAIT);
$request = $psr7->waitRequest();
if (!$request instanceof \Psr\Http\Message\ServerRequestInterface) {
break; // Worker 终止信号
}
} catch (Exception $ex) {
$psr7->respond(new Psr7\Response(400));
continue;
}
try {
// 响应中包含实时更新的全局变量
$body = 'Hello RoadRunner! Global counter: ' . $global_variable;
$psr7->respond(new Psr7\Response(200, [], $body));
} catch (Exception $ex) {
$psr7->respond(new Psr7\Response(500, [], 'Internal Error'));
}
}? 关键要点说明:
- ✅
Ev::run(Ev::RUN_NOWAIT)必须放在waitRequest()之前(或之后),且需在主循环内高频调用 —— 它不是“启动一次就完事”,而是“每次轮询时推进一次事件循环”。 - ✅ 定时器使用
EvTimer($after, $repeat, $cb),$repeat > 0即为周期性,无需手动重启。 - ⚠️
$global_variable是进程级变量:每个 RoadRunner Worker 进程拥有独立副本。如需跨进程共享(如统计总请求数),应改用 Redis、APCu(带锁)、或 RoadRunner 的KeyValue插件。 - ⚠️ 避免在 Ev 回调中执行阻塞操作(如
file_get_contents()、未设置超时的 cURL、长 SQL 查询)—— 否则会卡住整个 Worker,影响所有后续请求。 - ✅ 如需更健壮的后台任务(如队列消费、长连接管理),建议结合 RoadRunner 的
Jobs或GRPC插件,而非强依赖 Ev。
? 替代建议(面向复杂场景):
若项目已使用 ReactPHP 生态,可参考答案中给出的 React\EventLoop\Loop::addPeriodicTimer() 方案——但需注意:它要求整个应用基于 React 构建(包括 HTTP 服务),与 RoadRunner 的 Worker 模型不兼容。二者不可混用。RoadRunner 场景下,请坚持使用 ext-ev + 手动 Ev::run(Ev::RUN_NOWAIT) 轮询模式。
综上,RoadRunner 与 Ev 的协同本质是「协作式多任务」:通过在请求处理间隙主动让渡 CPU 给事件循环,实现后台逻辑与 HTTP 服务的并行演进。

















