服务事件 HOOK

ServerEventHook 是 Viswoole 对 Swoole 服务器生命周期事件的统一管理层,允许开发者在服务启动、Worker 启动、请求处理、关闭等关键节点注入自定义逻辑。

事件概述

支持的事件列表

事件名称触发时机参数
start主进程(Master)启动成功Server $server
shutdown服务器关闭Server $server
beforeshutdown服务器关闭前(可执行清理)Server $server
workerStartWorker 进程启动Server $server, int $workerId
workerStopWorker 进程停止Server $server, int $workerId
workerExitWorker 进程退出Server $server, int $workerId
request收到 HTTP 请求Request $request, Response $response
task收到 Task 任务Server $server, Task $task
finishTask 任务完成Server $server, int $taskId, string $data
pipeMessage收到管道消息Server $server, int $srcWorkerId, string $message
workerErrorWorker 进程异常Server $server, int $workerId, int $exitCode, int $signal
managerStart管理进程启动Server $server
managerStop管理进程停止Server $server

注册事件

方式一:配置文件注册(推荐)

config/server.phpevents 配置项中定义:

php
// config/server.php
return [
    'servers' => [
        'http' => [
            'events' => [
                // HTTP 请求处理(通常由框架内部注册)
                \Swoole\Constant::EVENT_REQUEST => [
                    \Viswoole\HttpServer\HttpEventHandle::class,
                    'onRequest'
                ],
            ],
        ],
    ],

    // 全局事件(所有服务共享)
    'events' => [
        'workerStart' => function (\Swoole\Server $server, int $workerId) {
            echo "Worker #{$workerId} 已启动\n";

            // 初始化数据库连接池
            // 预热缓存
            // 建立外部服务连接
        },
    ],
];

方式二:编程式注册

使用 ServerEventHook 静态方法动态注册:

php
use Viswoole\Core\Server\ServerEventHook;

// 注册单个事件
ServerEventHook::addEvent('workerStart', function ($server, $workerId) {
    // 自定义初始化逻辑
});

// 批量注册事件
ServerEventHook::addEvents([
    'workerStart' => function ($server, $workerId) { /* ... */ },
    'workerStop' => function ($server, $workerId) { /* ... */ },
]);

内置事件行为

框架预注册了以下基础事件处理:

onStart - 主进程启动

php
// 内部实现(简化)
private static function onStart(Server $server): void
{
    $pid = $server->getMasterPid();
    echo_log("🚀 服务已启动...({$pid})", SERVER_NAME, color: Output::LABEL_COLOR['SUCCESS']);

    // 监听 SIGINT 信号,安全关闭服务
    Process::signal(SIGINT, function () use ($server) {
        $server->shutdown();
    });

    // 触发 AfterStartServer 事件
    Event::emit('AfterStartServer', [$server]);
}

onBeforeShutdown - 关闭前

php
private static function onBeforeShutdown(Server $server): void
{
    // 触发 ServerShutdownBefore 事件
    Event::emit('ServerShutdownBefore', [$server]);
    // 允许用户在此执行清理工作
}

onShutdown - 关闭完成

php
private static function onShutdown(Server $server): void
{
    $pid = $server->getMasterPid();
    echo_log("🚫 服务已安全关闭({$pid})", SERVER_NAME);
}

常用场景

1. Worker 启动初始化

php
// config/server.php
'events' => [
    'workerStart' => function (\Swoole\Server $server, int $workerId) {
        // 仅对 Worker 进程(非 Task Worker)执行
        if (!$server->taskworker) {
            // 初始化数据库连接池
            Db::initPool(config('database'));

            // 预热热点缓存
            Cache::warmup(['system_config', 'permissions']);

            // 初始化定时器
            $this->registerTimers($server);
        }
    },
],

2. 优雅关闭清理

php
'events' => [
    'beforeshutdown' => function (\Swoole\Server $server) {
        // 关闭数据库连接
        Db::closeAll();

        // 刷新缓冲区日志
        Log::save(Log::getRecord());

        // 清理临时文件
        TempFileManager::cleanup();

        // 断开外部服务连接
        RedisPool::disconnectAll();
    },
]

3. 请求生命周期监控

php
'events' => [
    'request' => function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) {
        $startTime = microtime(true);

        // 在请求结束后记录指标(需要配合协程 defer)
        \Swoole\Coroutine::defer(function () use ($request, $startTime) {
            $duration = (microtime(true) - $startTime) * 1000;

            Metrics::record([
                'uri' => $request->server['request_uri'],
                'method' => $request->server['request_method'],
                'duration_ms' => round($duration, 2),
                'status' => http_response_code(),
            ]);
        });
    },
]

4. Task 任务监控

php
'events' => [
    'task' => function (\Swoole\Server $server, \Swoole\Server\Task $task) {
        $topic = $task->data['topic'] ?? 'unknown';

        Log::task('收到任务', [
            'topic' => $topic,
            'task_id' => $task->id,
            'worker_id' => $server->worker_id,
        ]);
    },

    'finish' => function (\Swoole\Server $server, int $taskId, string $data) {
        Log::task('任务完成', [
            'task_id' => $taskId,
            'result' => $data,
        ]);
    },
]

5. 定时任务注册

php
'events' => [
    'workerStart' => function (\Swoole\Server $server, int $workerId) {
        // 仅在第一个 Worker 中启动定时器
        if ($workerId === 0) {
            // 每 60 秒执行一次健康检查
            \Swoole\Timer::tick(60000, function () {
                HealthCheck::run();
            });

            // 每天凌晨 2 点执行数据统计
            $nextRun = strtotime('tomorrow 02:00:00') - time();
            \Swoole\Timer::after($nextRun * 1000, function () {
                Statistics::dailyReport();
                // 之后每 24 小时重复
                \Swoole\Timer::tick(86400000, function () {
                    Statistics::dailyReport();
                });
            });
        }
    },
]

与事件系统集成

ServerEventHook 会触发框架级别的业务事件,可在业务代码中监听:

php
use Viswoole\Core\Facade\Event;

// 监听服务启动完成事件
Event::on('AfterStartServer', function ($server) {
    // 服务完全就绪后的操作
    // 如:向注册中心注册服务
    ServiceRegistry::register();
});

// 监听服务关闭前事件
Event::on('ServerShutdownBefore', function ($server) {
    // 关闭前的最后操作
    // 如:从注册中心注销
    ServiceRegistry::deregister();
});

注意事项

1. 事件注册时机

事件必须在 服务启动前 注册完成。在 config/server.php 中配置或在 bootstrap 阶段通过 ServerEventHook::addEvent() 注册均可。

2. 多事件处理器

同一事件可以注册多个处理器,按注册顺序依次执行:

php
ServerEventHook::addEvent('workerStart', fn($s, $id) => /* 第一个 */);
ServerEventHook::addEvent('workerStart', fn($s, $id) => /* 第二个 */);
ServerEventHook::addEvent('workerStart', fn($s, $id) => /* 第三个 */);
// workerStart 时,以上三个回调将依次执行

3. 协程安全

在事件回调中使用协程相关功能时需注意:

php
'workerStart' => function ($server, $workerId) {
    // ✅ 正确:在 Worker 进程中创建协程
    \Swoole\Coroutine::create(function () {
        // 异步初始化操作
    });

    // ❌ 错误:Master 进程事件中不应使用协程
},

4. 性能影响

避免在高频事件(如 request)中执行重量级操作:

php
'request' => function ($request, $response) {
    // ❌ 避免:每次请求都查询数据库
    // $stats = DB::table('stats')->first();

    // ✅ 推荐:轻量级操作或使用内存缓存
    $counter = Counter::increment('requests');
}