28、Laravel 队列与事件开发全解析

Laravel 队列与事件开发全解析

在 Laravel 开发中,队列和事件是两个非常重要的功能,它们可以帮助我们优化应用程序的性能和响应速度。下面将详细介绍 Laravel 中队列和事件的使用方法。

队列的使用

队列在 Laravel 中主要用于处理一些耗时的任务,将这些任务放入队列中,由队列工作者(Queue Worker)异步处理,从而提高应用程序的响应速度。

1. 自定义队列

在队列服务器中,可以指定将作业推送到哪个命名队列。例如,可以根据队列的重要性进行区分,命名为 low high 。可以使用 onQueue() 方法自定义要将作业推送到的队列:

dispatch((new DoThingJob)->onQueue('high'));
2. 自定义延迟

可以使用 delay() 方法自定义队列工作者在处理作业之前应等待的时间,该方法接受一个整数,表示延迟作业的秒数:

// 延迟一分钟后将作业释放给队列工作者
dispatch((new DoThingJob)->delay(60));

需要注意的是,Amazon SQS 不允许延迟超过 15 分钟。

3. 运行队列工作者

队列工作者是一个 Artisan 命令,它会一直运行(直到手动停止),负责从队列中拉取作业并运行它们:

php artisan queue:work

这个命令会启动一个守护进程,“监听”队列。每当队列中有作业时,它会拉取第一个作业,处理它,删除它,然后继续处理下一个。如果队列中没有作业,它会“休眠”一段可配置的时间,然后再次检查是否有更多作业。

可以通过以下参数来定义队列工作者的行为:
- --timeout :作业在队列监听器停止之前可以运行的秒数。
- --sleep :当队列中没有作业时,监听器应“休眠”的秒数。
- --tries :每个作业在被删除之前允许尝试的次数。
- 第一个参数:队列工作者应监听的连接。
- --queue= :队列工作者应监听的队列。

例如:

php artisan queue:work redis --timeout=60 --sleep=15 --tries=3 --queue=high,medium

也可以使用 php artisan queue:work 只处理一个作业。

4. 错误处理

在处理作业时,如果发生错误,队列监听器会将该作业重新释放回队列,直到作业成功完成或达到最大尝试次数。

  • 限制尝试次数 :最大尝试次数由传递给 queue:listen queue:work Artisan 命令的 --tries 开关定义。如果不设置 --tries 或设置为 0,队列监听器将允许无限重试,这可能会导致应用程序因不断重试无法完成的作业而变慢。建议将最大重试次数的默认起始值设置为 3:
php artisan queue:listen --tries=3

可以使用 attempts() 方法检查作业已经尝试的次数:

public function handle()
{
    ...
    if ($this->attempts() > 3) {
        //
    }
}
  • 处理失败的作业 :一旦作业超过了允许的重试次数,就被视为“失败”的作业。在处理失败的作业之前,需要创建“失败作业”数据库表:
php artisan queue:failed-table
php artisan migrate

可以在作业类中定义 failed() 方法,当作业失败时运行该方法:

class CrunchReports implements ShouldQueue
{
    ...
    public function failed()
    {
        // Do whatever you want
    }
}

还可以注册一个全局的失败作业处理程序,在 AppServiceProvider boot() 方法中添加以下代码:

use Illuminate\Support\Facades\Queue;
...
public function boot()
{
    Queue::failing(function ($connection, $job, $data) {
        // Do whatever you want
    });
}

此外,还有一套 Artisan 工具用于与失败作业表进行交互:
- php artisan queue:failed :显示失败作业的列表。
- php artisan queue:retry <id> :重试指定 ID 的失败作业。
- php artisan queue:retry all :重试所有失败作业。
- php artisan queue:forget <id> :删除指定 ID 的失败作业。
- php artisan queue:flush :删除所有失败作业。

5. 控制队列

在处理作业时,可以添加条件来决定是否将作业释放回队列以便稍后重新启动,或者永久删除作业。
- 释放作业回队列 :使用 release() 命令:

public function handle()
{
    ...
    if (condition) {
        $this->release($numberOfSecondsToDelayBeforeRetrying);
    }
}
  • 删除作业 :在处理作业时,只需返回即可删除作业:
public function handle()
{
    ...
    if ($jobShouldBeDeleted) {
        return;
    }
}
6. 队列支持的其他功能

队列的主要用途是推送作业,但也可以使用 Mail::queue 功能对邮件进行排队,还可以对 Artisan 命令进行排队。

事件的使用

事件是一种通知机制,用于告知应用程序发生了某些事情。与作业不同,作业是告诉应用程序应该做什么,而事件是告诉应用程序某件事情已经发生。

1. 事件的概念

事件是一种通知,表明某件事情已经发生,例如 UserSubscribed UserSignedUp ContactWasAdded 。有些事件可能由框架本身触发,例如 Eloquent 模型在保存、创建或删除时会触发事件,也有些事件是由应用程序代码手动触发的。

2. 触发事件

有三种方式可以触发事件:
- 使用 Event 门面:

Event::fire(new UserSubscribed($user, $plan));
  • 注入 Dispatcher
$dispatcher = app(Illuminate\Contracts\Events\Dispatcher);
$dispatcher->fire(new UserSubscribed($user, $plan));
  • 使用 event() 全局辅助函数:
event(new UserSubscribed($user, $plan));

建议使用全局辅助函数。

可以使用 make:event Artisan 命令创建一个事件:

php artisan make:event UserSubscribed

生成的事件类默认模板如下:

<?php
namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class UserSubscribed
{
    use InteractsWithSockets, SerializesModels;
    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }
    /**
     * Get the channels the event should be broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

可以向事件中注入数据:

class UserSubscribed
{
    use InteractsWithSockets, SerializesModels;
    public $user;
    public $plan;
    public function __construct($user, $plan)
    {
        $this->user = $user;
        $this->plan = $plan;
    }
}
3. 监听事件

要监听事件,首先需要创建一个事件监听器。例如,每当有新用户订阅时,向应用程序的所有者发送电子邮件:

php artisan make:listener EmailOwnerAboutSubscription --event=UserSubscribed

生成的事件监听器默认模板如下:

<?php
namespace App\Listeners;
use App\Events\UserSubscribed;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailOwnerAboutSubscription
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }
    /**
     * Handle the event.
     *
     * @param  UserSubscribed  $event
     * @return void
     */
    public function handle(UserSubscribed $event)
    {
        //
    }
}

可以修改 handle() 方法来实现具体的逻辑,例如发送电子邮件:

use Illuminate\Contracts\Mail\Mailer;
class EmailOwnerAboutSubscription
{
    protected $mailer;
    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }
    public function handle(UserSubscribed $event)
    {
        $this->mailer->send(
            new OwnerSubscriptionEmail($event->user, $event->plan)
        );
    }
}

最后,需要在 EventServiceProvider 类的 $listen 属性中设置监听器来监听事件:

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        \App\Events\UserSubscribed::class => [
            \App\Listeners\EmailOwnerAboutSubscription::class,
        ],
    ];
4. 事件订阅者

Laravel 还提供了事件订阅者的概念,它是一个包含多个方法的类,这些方法作为独立的监听器来处理不同的事件,并且包含了哪个方法应该处理哪个事件的映射。

示例事件订阅者如下:

<?php
namespace App\Listeners;
class UserEventSubscriber
{
    public function onUserSubscription($event)
    {
        // Handles the UserSubscribed event
    }
    public function onUserCancellation($event)
    {
        // Handles the UserCancelled event
    }
    public function subscribe($events)
    {
        $events->listen(
            \App\Events\UserSubscribed::class,
            'App\Listeners\UserEventSubscriber@onUserSubscription'
        );
        $events->listen(
            \App\Events\UserCancelled::class,
            'App\Listeners\UserEventSubscriber@onUserCancellation'
        );
    }
}

需要在 App\Providers\EventServiceProvider 中添加订阅者的类名到 $subscribe 属性:

class EventServiceProvider extends ServiceProvider
{
    ...
    protected $subscribe = [
        \App\Listeners\UserEventSubscriber::class
    ];
}
事件通过 WebSockets 广播和 Laravel Echo

WebSocket 是一种由 Pusher 推广的协议,它可以在 Web 设备之间提供近乎实时的通信。Laravel 内置了一套工具,使得将一个或多个事件广播到 WebSocket 服务器变得容易。

1. WebSocket 协议

WebSocket 协议通过直接在客户端和服务器之间打开连接,避免了依赖 HTTP 请求进行信息传递,像 Gmail 和 Facebook 的聊天框等工具就是基于 WebSockets 实现的。

2. Laravel Echo

Laravel Echo 是一个更强大的工具,用于更复杂的事件广播。如果需要存在通知,或者希望保持丰富的前端数据模型与 Laravel 应用同步,可以使用 Laravel Echo。

3. 配置和设置

可以在 config/broadcasting.php 中找到事件广播的配置设置。Laravel 支持三种广播驱动:Pusher(付费 SaaS 服务)、Redis(本地运行的 WebSocket 服务器)和 log(用于本地开发和调试)。

为了使事件广播快速进行,Laravel 将广播指令推送到队列中,因此需要运行一个队列工作者(或在本地开发时使用同步队列驱动)。Laravel 建议队列工作者在查找新作业之前默认延迟 3 秒,但在事件广播中,可能会发现某些事件需要一两秒才能广播。为了加快速度,可以将队列设置更新为只等待 1 秒再查找新作业。

4. 广播事件

要广播事件,需要让事件类实现 Illuminate\Contracts\Broadcasting\ShouldBroadcast 接口,并添加 broadcastOn() 方法,该方法将返回一个字符串数组或 Channel 对象数组,每个对象代表一个 WebSocket 通道。

示例:

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class UserSubscribed extends Event implements ShouldBroadcast
{
    use InteractsWithSockets, SerializesModels;
    public $user;
    public $plan;
    public function __construct($user, $plan)
    {
        $this->user = $user;
        $this->plan = $plan;
    }
    public function broadcastOn()
    {
        // String syntax
        return [
            'users.' . $this->user->id,
            'admins'
        ];
        // Channel object syntax
        return [
            new Channel('users.' . $this->user->id),
            new Channel('admins'),
            // If it were a private channel: new PrivateChannel('admins'),
            // If it were a presence channel: new PresenceChannel('admins'),
        ];
    }
}

默认情况下,事件的任何公共属性将被序列化为 JSON 并作为广播事件的数据发送。可以通过 broadcastWith() 方法返回一个数据数组来覆盖默认行为:

public function broadcastWith()
{
    return [
        'userId' => $this->user->id,
        'plan' => $this->plan
    ];
}

还可以使用 onQueue() 方法自定义事件被推送到的队列:

public function onQueue()
{
    return 'websockets-for-faster-processing'
}
5. 接收消息

如果选择自己托管 Redis WebSockets 服务器,Laravel 文档中有关于如何使用 socket.io ioredis 设置它的详细说明。但更常见的是使用 Pusher,它有一个慷慨的免费计划,并且其 JavaScript SDK 几乎不需要你做任何工作就能处理所有的身份验证和通道管理。

可以使用以下代码来订阅事件:

<script src="https://js.pusher.com/3.1/pusher.min.js"></script>
<script>
// Globally, perhaps; just a sample of how to get data in
var App = {
    'userId': 5,
    'pusherKey': 'your-pusher-api-key-here'
};
// Locally
var pusher = new Pusher(App.pusherKey);
var pusherChannel = pusher.subscribe('users.' + App.userId);
pusherChannel.bind('App\\Events\\UserSubscribed', (data) => {
    console.log(data.user, data.plan);
});
</script>

在 JavaScript 中,由于 \ 是控制字符,需要使用 \\ 来表示反斜杠。

要从 Laravel 向 Pusher 发布事件,需要从 Pusher 账户仪表板获取 Pusher 密钥、秘密和应用 ID,并将它们设置在 .env 文件中。

通过以上介绍,我们详细了解了 Laravel 中队列和事件的使用方法,以及如何通过 WebSockets 进行事件广播。合理使用这些功能可以大大提高应用程序的性能和响应速度。

Laravel 队列与事件开发全解析(续)

Laravel Echo 的进一步应用

前面提到 Laravel Echo 是用于更复杂事件广播的强大工具,下面详细探讨其使用。

1. Laravel Echo 的优势

Laravel Echo 能提供存在通知功能,比如可以实时知道哪些用户在线。同时,它有助于保持前端数据模型与 Laravel 应用同步,让前端开发者更方便地处理后端事件。

2. 引入 Laravel Echo

要使用 Laravel Echo,首先需要引入其 JavaScript 库。可以通过 npm 进行安装:

npm install --save laravel-echo pusher-js

安装完成后,在前端代码中进行配置。在 resources/js/bootstrap.js 文件中添加以下代码:

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    encrypted: true
});

这里使用 Pusher 作为广播器,需要在 .env 文件中配置 Pusher 的相关信息。

3. 使用 Laravel Echo 监听事件

配置好 Laravel Echo 后,就可以使用它来监听事件。例如,监听 UserSubscribed 事件:

window.Echo.channel('users.' + userId)
    .listen('UserSubscribed', (e) => {
        console.log(e.user, e.plan);
    });

这里通过 channel 方法指定要监听的通道, listen 方法指定要监听的事件。

队列与事件的流程图分析

下面通过 mermaid 流程图来展示队列和事件的主要流程。

1. 队列处理流程
graph TD;
    A[作业创建] --> B[作业入队];
    B --> C{队列是否有作业};
    C -- 有作业 --> D[队列工作者拉取作业];
    D --> E[处理作业];
    E -- 成功 --> F[删除作业];
    E -- 失败 --> G[重新入队];
    G --> C;
    C -- 无作业 --> H[队列工作者休眠];
    H --> C;

这个流程图展示了队列作业的处理过程。作业创建后入队,队列工作者不断检查队列是否有作业,有则拉取处理,成功则删除,失败则重新入队。无作业时队列工作者会休眠一段时间后再次检查。

2. 事件处理流程
graph TD;
    A[事件触发] --> B{是否有监听器};
    B -- 有监听器 --> C[监听器响应事件];
    B -- 无监听器 --> D[事件无响应];
    C --> E[执行监听器逻辑];

事件触发后,会检查是否有监听器。如果有监听器,监听器会响应事件并执行相应逻辑;如果没有监听器,事件则无响应。

队列和事件的性能优化

在实际应用中,为了提高队列和事件的性能,需要进行一些优化。

1. 队列性能优化
  • 合理设置队列参数 :根据业务需求,合理设置 --timeout --sleep --tries 等参数。例如,对于一些对实时性要求较高的作业,可以将 --timeout 设置得小一些,避免作业长时间占用资源。
  • 使用多个队列 :根据作业的重要性和类型,使用不同的队列。例如,将重要的作业放在 high 队列,次要的作业放在 low 队列,让队列工作者分别处理不同队列的作业,提高处理效率。
2. 事件性能优化
  • 减少不必要的监听器 :只添加必要的监听器,避免过多的监听器导致性能下降。
  • 优化监听器逻辑 :在监听器的 handle 方法中,尽量减少复杂的计算和数据库操作,提高响应速度。
队列和事件的实际应用场景

队列和事件在很多实际场景中都有广泛应用,下面介绍一些常见的场景。

1. 队列的应用场景
  • 邮件发送 :将邮件发送任务放入队列中,由队列工作者异步处理,避免在用户请求时直接发送邮件导致响应时间过长。例如:
dispatch((new SendEmailJob($user, $email))->onQueue('email'));
  • 文件处理 :对于一些大文件的处理,如文件上传后的压缩、转换等操作,可以将这些任务放入队列中,避免阻塞主线程。
2. 事件的应用场景
  • 用户注册通知 :当用户注册成功后,触发 UserRegistered 事件,通过监听器发送欢迎邮件、通知管理员等。
event(new UserRegistered($user));
  • 订单状态更新 :当订单状态发生变化时,触发 OrderStatusChanged 事件,更新相关的缓存、通知用户等。
总结

通过本文的介绍,我们全面了解了 Laravel 中队列和事件的使用方法,包括队列的自定义、延迟、错误处理、控制等,事件的触发、监听、订阅者以及通过 WebSockets 进行广播等。同时,还探讨了 Laravel Echo 的应用、队列和事件的流程图分析、性能优化以及实际应用场景。

合理运用队列和事件功能,可以将耗时任务异步处理,提高应用程序的性能和响应速度,实现更高效的开发。在实际项目中,开发者应根据具体需求,灵活配置和使用这些功能,以达到最佳的开发效果。

根据原作 https://pan.quark.cn/s/459657bcfd45 的源码改编 Classic-ML-Methods-Algo 引言 建立这个项目,是为了梳理和总结传统机器学习(Machine Learning)方法(methods)或者算法(algo),和各位同仁相互学习交流. 现在的深度学习本质上来自于传统的神经网络模型,很大程度上是传统机器学习的延续,同时也在不少时候需要结合传统方法来实现. 任何机器学习方法基本的流程结构都是通用的;使用的评价方法也基本通用;使用的一些数学知识也是通用的. 本文在梳理传统机器学习方法算法的同时也会顺便补充这些流程,数学上的知识以供参考. 机器学习 机器学习是人工智能(Artificial Intelligence)的一个分支,也是实现人工智能最重要的手段.区别于传统的基于规则(rule-based)的算法,机器学习可以从数据中获取知识,从而实现规定的任务[Ian Goodfellow and Yoshua Bengio and Aaron Courville的Deep Learning].这些知识可以分为四种: 总结(summarization) 预测(prediction) 估计(estimation) 假想验证(hypothesis testing) 机器学习主要关心的是预测[Varian在Big Data : New Tricks for Econometrics],预测的可以是连续性的输出变量,分类,聚类或者物品之间的有趣关联. 机器学习分类 根据数据配置(setting,是否有标签,可以是连续的也可以是离散的)和任务目标,我们可以将机器学习方法分为四种: 无监督(unsupervised) 训练数据没有给定...
本系统采用微信小程序作为前端交互界面,结合Spring BootVue.js框架实现后端服务及管理后台的构建,形成一套完整的电子商务解决方案。该系统架构支持单一商户独立运营,亦兼容多商户入驻的平台模式,具备高度的灵活性扩展性。 在技术实现上,后端以Java语言为核心,依托Spring Boot框架提供稳定的业务逻辑处理数据接口服务;管理后台采用Vue.js进行开发,实现了直观高效的操作界面;前端微信小程序则为用户提供了便捷的移动端购物体验。整套系统各模块间紧密协作,功能链路完整闭环,已通过严格测试优化,符合商业应用的标准要求。 系统设计注重业务场景的面覆盖,不仅包含商品展示、交易流程、订单处理等核心电商功能,还集成了会员管理、营销工具、数据统计等辅助模块,能够满足不同规模商户的日常运营需求。其多店铺支持机制允许平台方对入驻商户进行统一管理,同时保障各店铺在品牌展示、商品销售及客户服务方面的独立运作空间。 该解决方案强调代码结构的规范性可维护性,遵循企业级开发标准,确保了系统的长期稳定运行后续功能迭代的可行性。整体而言,这是一套技术选型成熟、架构清晰、功能完备且可直接投入商用的电商平台系统。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值