Laravel 事件广播:实时通知、WebSocket 与队列驱动架构

Laravel 事件广播:事件系统(Event/Listener/Subscriber)、广播机制(public/private/channel)、WebSocket 集成(Laravel Echo/Pusher/Soketi/Ably)、队列驱动(Redis/Database/SQS 配置与监控)、广播频道授权(Broadcast::channel 闭包)、Presence Channel(在线状态)、通知系统(Notification/multicast/邮件/Slack/短信)、实时应用场景(聊天/通知/仪表盘)、性能与扩展策略。

引言

「实时」是现代 Web 应用的标配——新消息弹出、订单状态变化、协作者光标移动。Laravel 的事件广播系统让 PHP 后端天然支持实时推送:事件触发 → 广播到频道 → 前端通过 WebSocket 即时收到。本文从 Laravel 的事件/监听器体系出发,深入广播机制(public/private/presence)、WebSocket 集成(Pusher/Soketi/Ably)、队列驱动与通知系统——给 PHP 团队一份实时架构的完整地图。

前置:/php-laravel-authentication/(认证授权)、/php-observability-logging-tracing/(可观测性)。


目录


1. Laravel 事件系统:从触发到监听

1.1 基础定义

// app/Events/OrderShipped.php
class OrderShipped {
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public function __construct(public Order $order) {}
}

// app/Listeners/SendShipmentNotification.php
class SendShipmentNotification {
    public function handle(OrderShipped $event): void {
        // 发货通知逻辑
    }
}

1.2 触发事件

// 同步触发(阻塞)
event(new OrderShipped($order));

// 异步触发(放入队列)
OrderShipped::dispatch($order);

// 延迟触发
OrderShipped::dispatch($order)->delay(now()->addMinutes(10));

1.3 事件 vs 监听器的组织

# 事件 = "发生了什么事"(名词+过去式:OrderShipped/UserRegistered)
# 监听器 = "听到后要做什么"(动词:SendEmail/UpdateAnalytics)
# 一个事件可有一个监听器,一个监听器可监听多个事件(Subscriber)

记忆 事件 = “发生的事”(OrderShipped),监听器 = “听后做什么”(SendEmail);同步用 event()、异步用 dispatch()、延迟用 ->delay();事件与监听器解耦,一个事件可多监听。


2. 广播基础:频道类型与消息格式

2.1 三种频道

public:任何人可订阅(如公开聊天室)
private:需认证才能订阅(如用户个人通知)
presence:私密 + 显示"谁在线"(如协作文档)

2.2 事件实现广播

class OrderShipped implements ShouldBroadcast {
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public Order $order) {}

    public function broadcastOn(): array {
        return [
            new PrivateChannel('orders.' . $this->order->user_id),
        ];
    }

    public function broadcastAs(): string {
        return 'order.shipped';  # 前端监听的事件名
    }

    public function broadcastWith(): array {
        return ['order_id' => $this->order->id];
    }
}

2.3 广播原理

PHP 触发事件 → 写入广播驱动(Redis/Pusher/Ably)→ WebSocket 服务器推送 → 前端收到
# 默认 Laravel 用队列异步推送,避免阻塞主请求

记忆 三频道——public(开放)、private(需认证)、presence(在线状态);事件实现 ShouldBroadcast,broadcastOn 定义频道、broadcastAs 定义事件名、broadcastWith 定义 Payload;推送走队列异步,防阻塞请求。


3. WebSocket 集成:Pusher、Soketi 与 Ably

3.1 选型对比

方案类型适用成本
PusherSaaS快速上线、流量中等按并发连接/消息
AblySaaS企业级、高可用按消息量
Soketi自建技术栈可控、大流量服务器成本
Laravel Reverb自建Laravel 原生、简单部署服务器成本

3.2 Pusher 配置

# .env
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=xxx
PUSHER_APP_KEY=xxx
PUSHER_APP_SECRET=xxx
PUSHER_APP_CLUSTER=ap1

# config/broadcasting.php
'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => ['cluster' => 'ap1', 'useTLS' => true],
],

3.3 Soketi 自建(Node.js WebSocket)

# 安装
npm install -g @soketi/soketi
soketi start

# Laravel 配置
default => env('BROADCAST_DRIVER', 'pusher'),
pusher => [
    'driver' => 'pusher',
    'key' => 'app-key',
    'secret' => 'app-secret',
    'app_id' => 'app-id',
    'options' => ['host' => '127.0.0.1', 'port' => 6001, 'scheme' => 'http'],
],

3.4 Laravel Reverb(PHP 原生)

# Laravel 11+ 内置
php artisan reverb:install
php artisan reverb:start

# .env
BROADCAST_DRIVER=reverb
# Reverb 是纯 PHP WebSocket 服务器,无需 Node.js

记忆 WebSocket 选型——Pusher/Ably SaaS 快速上线、Soketi 自建 Node.js、Reverb PHP 原生(Laravel 11+);Pusher 最简单(改配置即可)、Reverb 最 Laravel 原生(php artisan start)。


4. Laravel Echo 前端实时客户端

4.1 安装与配置

// npm install laravel-echo pusher-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,
    forceTLS: true,
});

4.2 订阅频道

// Public Channel
Echo.channel('orders')
    .listen('OrderShipped', (e) => {
        console.log(e.order);
    });

// Private Channel(自动带认证)
Echo.private('orders.' + userId)
    .listen('.order.shipped', (e) => {  # 注意 broadcastAs 带点前缀
        showNotification('Order ' + e.order_id + ' shipped!');
    });

// Presence Channel
Echo.join('chat.' + roomId)
    .here((users) => { console.log('Online:', users); })
    .joining((user) => { console.log(user.name + ' joined'); })
    .leaving((user) => { console.log(user.name + ' left'); });

4.3 Echo 与 Vue/React

# Vue:在 mounted() 中订阅,beforeUnmount() 中取消订阅
# React:useEffect(() => { ...; return () => { Echo.leave() }; }, [])
# 避免内存泄漏:组件卸载时一定 leave 频道

记忆 Echo 是前端 WebSocket 客户端——channel 订阅 public、private 自动带认证、join 用于 presence;listen 事件名带 . 前缀(broadcastAs 的);组件卸载时 leave 防内存泄漏。


5. 频道授权与 Presence Channel

5.1 频道授权

// routes/channels.php
Broadcast::channel('orders.{userId}', function (User $user, int $userId) {
    return $user->id === $userId;  # true=授权,false=拒绝
});

Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
    return ['id' => $user->id, 'name' => $user->name];  # presence 返回用户信息
});

5.2 Presence Channel 数据

# Echo.join 后 .here() 返回当前在线用户列表
# .joining() / .leaving() 监听进出
# 典型应用:在线文档协作者列表、实时聊天室成员

5.3 授权最佳实践

# 频道名包含资源 ID(orders.123)避免越权
# 授权闭包只做快速判断(查缓存而非 DB)
# 复杂权限用 Gate/Policy:return Gate::allows('view', $order);

记忆 频道授权在 routes/channels.php 中定义——返回 true/false 控制访问、返回数组数据给 presence;频道名含资源 ID 防越权、授权闭包要快(查缓存)、复杂权限用 Gate。


6. 队列驱动:Redis、Database 与监控

6.1 驱动配置

# .env
QUEUE_CONNECTION=redis
# 可选:database / sqs / beanstalkd

# Redis 队列(推荐生产)
# 支持:horizon 监控 + 失败重试 + 延迟任务
# 需: predis/predis 或 php-redis 扩展

6.2 Horizon 监控(Redis 队列)

composer require laravel/horizon
php artisan horizon:install
php artisan horizon

# Dashboard:/horizon
# 功能:吞吐量、等待时间、失败任务、负载均衡、信号优雅重启

6.3 队列 Worker 管理

# Supervisor 配置(生产环境)
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
numprocs=4

# 信号:
# SIGTERM → 当前任务完成后退出(优雅)
# SIGKILL → 立即终止(危险)

记忆 队列驱动生产用 Redis + Horizon 监控;Worker 用 Supervisor 管理 4-8 进程;信号 SIGTERM 优雅退出、SIGKILL 危险;Horizon 看吞吐量/等待/失败/负载。


7. 通知系统:Notification 与多渠道投递

7.1 创建通知

// php artisan make:notification OrderShippedNotification
class OrderShippedNotification extends Notification {
    public function via($notifiable): array {
        return ['mail', 'database', 'broadcast'];  # 多渠道
    }

    public function toMail($notifiable): MailMessage {
        return (new MailMessage)
            ->subject('Your order has shipped!')
            ->line('Order #' . $notifiable->order_id)
            ->action('Track', url('/orders/' . $notifiable->order_id));
    }

    public function toDatabase($notifiable): array {
        return ['order_id' => $notifiable->order_id, 'status' => 'shipped'];
    }

    public function toBroadcast($notifiable): BroadcastMessage {
        return new BroadcastMessage(['order_id' => $notifiable->order_id]);
    }
}

7.2 发送通知

$user->notify(new OrderShippedNotification($order));

# 或批量通知(避免 N+1)
Notification::send($users, new OrderShippedNotification($order));

7.3 通知频道

频道用途
mail邮件通知
database站内通知(存 notifications 表)
broadcast实时推送
slackSlack 消息
vonage/sms短信通知

记忆 通知用 make:notification 创建,via() 定义渠道(mail/database/broadcast/slack);toXxx() 方法各渠道内容分离;批量发送用 Notification::send() 防 N+1。


8. 实时应用场景与架构模式

8.1 场景地图

场景技术方案频道类型
站内通知Notification + broadcastprivate
聊天室Event + broadcastpresence
实时仪表盘Event + broadcastprivate
协作编辑Presence + CRDTpresence
订单状态更新Notification + SMS + broadcastprivate

8.2 架构模式

# 模式 1:通知中心
Event → Notification → [mail + database + broadcast]
前端 Echo.private 订阅,收到后 toast 显示

# 模式 2:聊天室
MessageSent Event → broadcastOn(presence-channel)
前端 Echo.join 订阅,.here/.joining/.leaving 管理成员

# 模式 3:状态推送
OrderStatusChanged → broadcast + queue 异步通知
# 订单量大时用批量聚合(5 秒内同一用户的多条通知合并)

记忆 实时场景——站内通知用 Notification 多渠道、聊天室用 presence、仪表盘用 private+轮询互补、协作编辑用 presence+CRDT;大量通知时做批量聚合。


9. 性能与扩展策略

9.1 避免广播雪崩

# 问题:1 万人在线,广播一条消息 → 1 万次推送
# 解决:
#   - 聚合:5 秒内同一用户的多条通知合并
#   - 限流:高频事件抽样(心跳改为 30s)
#   - 分区:按用户 ID hash 到不同频道

9.2 扩展 Redis

# 单机 Redis 瓶颈:连接数、内存
# 扩展:Redis Cluster / Sentinel
# 或:用专业消息队列(RabbitMQ / Kafka)替代 Redis 做广播后端

9.3 连接数优化

# WebSocket 连接数 = 同时在线用户数
# 优化:
#   - 页面不可见时断开 Echo(document.visibilitychange)
#   - 长轮询降级(Pusher fallback)
#   - 按业务分独立 WebSocket 集群

记忆 性能策略——聚合高频通知、限流抽样、分区频道;Redis 单机瓶颈用 Cluster/Sentinel 或换 RabbitMQ/Kafka;连接数优化用可见性监听断开+长轮询降级+独立集群。


10. 速查表与一句话记忆

概念一句话
Event发生了什么
Listener听后做什么
broadcastOn定义频道
broadcastAs事件名
public/private/presence三频道类型
PusherSaaS 快速
ReverbPHP 原生
Echo前端客户端
Notification多渠道通知
HorizonRedis 队列监控

一句话记忆:Laravel 事件系统——Event「发生了什么」+ Listener「听后做什么」+ dispatch 异步触发;广播实现 ShouldBroadcast,broadcastOn 定义频道、broadcastAs 定义事件名;三频道 public(开放)/private(需认证)/presence(在线状态);WebSocket 选型——Pusher SaaS 最快、Reverb PHP 原生、Soketi 自建 Node.js;Echo 前端订阅频道,组件卸载时 leave 防泄漏;频道授权在 routes/channels.php 中返回 true/false,含资源 ID 防越权;队列生产用 Redis + Horizon 监控,Worker 用 Supervisor 管理;Notification 多渠道(mail/database/broadcast/slack),批量 send 防 N+1;实时场景站内通知/聊天室/仪表盘各取频道类型,大量通知做聚合限流——「事件是解耦的胶水,广播让后端触达前端,队列保证不阻塞主请求」。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「php」更多文章

  1. PHP 数据库迁移治理:架构设计、版本控制与多环境管理
  2. WordPress 开发实战:主题定制、插件架构与 Headless CMS
  3. Laravel Livewire 交互组件实战:实时表单、表格与动态界面