基于 Swoole 在 Laravel 中实现异步事件监听及处理
这一篇教程我们直接以 hhxsv5/laravel-s 扩展包为例,演示如何在 Laravel 项目中基于 Swoole 实现事件监听。
系统自带事件
首先我们来看一些扩展包自带的系统事件,这些系统事件又可以进一步划分为 Laravel 应用级别的系统事件以及 Swoole 底层 Worker 进程级别的系统事件。
注:在基于 Swoole HTTP 服务器的系统中,Worker 进程相当于 Nginx + PHP-FPM 组合中的 PHP-FPM,关于 Swoole 的底层实现细节后面我们会详细介绍。
应用级别系统事件
首先我们来看扩展包自带的 Laravel 应用请求生命周期中的两个系统事件,分别是 laravels.received_request
和 laravels.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_key
和 post_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
字段了:
进程级别系统事件
此外扩展包还提供了几个 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.php
的 event_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.php
的 events
配置项中配置自定义事件与事件监听器的映射关系(一个事件可以绑定多个监听器):
'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 原生支持异步任务,极大简化了异步任务和异步事件监听和处理的实现流程。
9 Comments
WorkerStartEventListener应该在app/Listeners目录下,文中写成app/Events
Illuminate\Contracts\Container\BindingResolutionException: Target class [swoole] does not exist . 报了这个错误
自定义异步事件监听及处理 这块现在用法变了。我用的是
LaravelS 3.7.0
自定义异步事件监听及处理,监听器现在要添加到类的
$listeners
属性中,感觉好不方便。 参见laravel-s文档自定义的异步事件,当前版本为LaravelS 3.7.0
。自定义异步事件监听最新版已经修改了,学员君要不要更新一下文档呀
我平时没怎么关注 Swoole ?
这篇文章写得是真不用心
laravels.generated_response 事件监听处理函数中应该传入 \Symfony\Component\HttpFoundation\Response
现在的版本不需要在 “配置文件 config/laravels.php 的 event_handlers 配置项中添加事件与事件监听器的映射关系”,而是在 TestEvent 中配置