基于 Swoole 在 Laravel 中实现异步事件监听及处理


这一篇教程我们直接以 hhxsv5/laravel-s 扩展包为例,演示如何在 Laravel 项目中基于 Swoole 实现事件监听。

系统自带事件

首先我们来看一些扩展包自带的系统事件,这些系统事件又可以进一步划分为 Laravel 应用级别的系统事件以及 Swoole 底层 Worker 进程级别的系统事件。

注:在基于 Swoole HTTP 服务器的系统中,Worker 进程相当于 Nginx + PHP-FPM 组合中的 PHP-FPM,关于 Swoole 的底层实现细节后面我们会详细介绍。

应用级别系统事件

首先我们来看扩展包自带的 Laravel 应用请求生命周期中的两个系统事件,分别是 laravels.received_requestlaravels.generated_response,我们可以通过监听这两个事件来重置或销毁一些全局(global)或静态(static)变量,或者修改当前的请求或响应对象。

laravels.received_request

laravels.received_request 事件会在 LaravelS 扩展包将 Swoole\Http\Request 请求实例转化为 Illuminate\Http\Request 时触发(源码位于 vendor/hhxsv5/laravel-s/src/LaravelS.php):

public function onRequest(SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) 
{
    try {
        parent::onRequest($swooleRequest, $swooleResponse);
        $laravelRequest = $this->convertRequest($this->laravel, $swooleRequest);
        $this->laravel->bindRequest($laravelRequest);
        $this->laravel->fireEvent('laravels.received_request', [$laravelRequest]);

        ...

由于请求已经转化为 Laravel 请求实例,也将相应的实例信息绑定到 Laravel 容器,并且该事件是通过 Laravel 框架内置的事件分发机制触发的,所以我们可以基于 Laravel 的事件监听机制监听该事件并进行处理,比如,在 app/Providers/EventServiceProvider.php 服务提供者的 boot 方法中添加如下事件监听处理代码:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;

Event::listen('laravels.received_request', function (Request $request, $app) {
    $request->query->set('get_key', 'swoole-get-param');// 修改 GET 请求参数
    $request->request->set('post_key', 'swoole-post-param'); // 修改 POST 请求参数
});       

这个时候我们重新加载一下 Swoole HTTP 服务器,就可以从请求实例中获取到 get_keypost_key 参数的值了。

laravels.generated_response

laravels.generated_response 事件会在 Laravel Kernel 处理完请求,扩展包将 Illuminate\Http\Response 响应实例转化为 Swoole\Http\Response 前触发(源码位于 vendor/hhxsv5/laravel-s/src/LaravelS.php,感兴趣的同学自己看下,在处理静态资源和动态资源时都会触发),因此仍然在 Laravel 请求生命周期之内,由于该事件也是通过 Laravel 内置的事件分发机制触发的,所以可以按照 Laravel 监听事件的方式对其进行处理,还是在 app/Providers/EventServiceProvider.php 服务提供者的 boot 方法最后添加如下事件监听处理代码如下:

use Illuminate\Http\Response;

Event::listen('laravels.generated_response', function (Request $request, Response $response, $app) {
    $response->headers->set('header-key', 'swoole-header');
});

重新加载 Swoole HTTP 服务器,通过域名 todo-s.test 请求任意路由,就可以看到响应头中包含 header-key 字段了:

Swoole + Laravel 事件监听

进程级别系统事件

此外扩展包还提供了几个 Worker 进程级别的系统事件:

事件名 事件监听器接口 触发时间
WorkerStart Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface 当 Worker/Task 进程启动时触发,此时 Laravel 初始化已经完成
WorkerStop Hhxsv5\LaravelS\Swoole\Events\WorkerStopInterface 当 Worker/Task 进程正常退出时触发
WorkerError Hhxsv5\LaravelS\Swoole\Events\WorkerErrorInterface 当 Worker/Task 进程出现异常或错误时触发

由于这几个事件不是通过 Laravel 框架内置的事件分发机制触发,所以需要通过额外的监听机制来实现事件的监听和处理,在 LaravelS 扩展包支持的体系中,我们需要创建实现该事件对应事件监听器接口的事件监听器类,以 WorkerStart 事件为例,对应要实现的接口是 Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface,我们在 app/Events 目录下创建实现该接口的对应事件监听器类 WorkerStartEventListener,并编写事件处理代码如下:

<?php
namespace App\Listeners;

use Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface;
use Illuminate\Support\Facades\Log;
use Swoole\Http\Server;

class WorkerStartEventListener implements WorkerStartInterface
{
    public function __construct()
    {
    }

    public function handle(Server $server, $workerId)
    {
        Log::info('Worker/Task Process Started');
    }
}

然后在配置文件 config/laravels.phpevent_handlers 配置项中添加事件与事件监听器的映射关系:

'event_handlers' => [
    'WorkerStart' => \App\Listeners\WorkerStartEventListener::class,
],

重新启动 Swoole HTTP 服务器,就可以在 storage/logs 目录下最新的 Laravel 日志中看到 Worker/Task Process Started 日志。

自定义异步事件监听及处理

除了扩展包自带的系统事件外,还可以编写自定义的异步事件,该特性基于 Swoole 的异步任务,首先需要在配置文件 config/laravels.php 中设置 swoole.task_worker_num(我们在上一篇教程中已经设置过)。

要实现异步事件监听,需要创建一个继承自 Hhxsv5\LaravelS\Swoole\Task\Event 基类的事件类,我们在 app/Events 目录下创建这个类,类名为 TestEvent,初始化该事件类的代码如下:

<?php

namespace App\Events;

use Hhxsv5\LaravelS\Swoole\Task\Event;

class TestEvent extends Event
{
    private $data;

    public function __construct($data)
    {
        $this->data = $data;
    }

    public function getData()
    {
        return $this->data;
    }
}

然后在 app/Listeners 目录下创建一个监听该事件的监听器类 TestEventListener,该监听器继承自 Hhxsv5\LaravelS\Swoole\Task\Listener 基类,初始化监听器代码如下:

<?php

namespace App\Listeners;

use App\Events\TestEvent;
use Hhxsv5\LaravelS\Swoole\Task\Event;
use Hhxsv5\LaravelS\Swoole\Task\Listener;
use Illuminate\Support\Facades\Log;

class TestEventListener extends Listener
{
    public function __construct()
    {
    }

    public function handle(Event $event)
    {
        Log::info(__CLASS__ . ': 开始处理', [$event->getData()]);
        sleep(3);// 模拟耗时代码的执行
        Log::info(__CLASS__ . ': 处理完毕');
    }
}

接下来,我们需要在配置文件 config/laravels.phpevents 配置项中配置自定义事件与事件监听器的映射关系(一个事件可以绑定多个监听器):

'events' => [
    \App\Events\TestEvent::class => [
        \App\Listeners\TestEventListener::class,
    ]
],

最后,我们在 routes/web.php 中编写一段测试代码来测试事件的触发:

Route::get('/event/test', function () {
    $event = new \App\Events\TestEvent('测试异步事件监听及处理');
    $success = \Hhxsv5\LaravelS\Swoole\Task\Event::fire($event);
    var_dump($success);
});

重新启动 Swoole HTTP 服务器,在浏览器中访问 http://todo-s.test/event/test,页面会立即返回,对应的事件处理则会异步执行,你可以在 storage/logs 目录下最新的 Laravel 日志中看到相应的事件处理日志。

以上就是 Laravel 中基于 Swoole 实现异步事件监听及处理的简单示例,相较于原生 Laravel 自带的那套推送任务/事件到队列再通过 PHP CLI 启动新进程异步消费队列任务的逻辑,Swoole 原生支持异步任务,极大简化了异步任务和异步事件监听和处理的实现流程。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 基于 Swoole 在 Laravel 中实现异步任务队列

>> 下一篇: 通过 Process 模块在 PHP 中实现多进程(一):简单的多进程 TCP 服务器实现