多服务器部署Laravel如何通过Redis实现事件在所有服务器触发监听
问题根源
你当前的实现逻辑混淆了Laravel队列的抢占消费模式和跨服务全量广播的需求:
- Laravel普通Redis队列是「多消费者竞争消费」设计,同一个队列任务只会被其中一个服务器的队列worker抢到并执行,所以自然只有一台服务器的日志有记录
- 你实现的
ShouldBroadcast接口是Laravel Websocket前端广播用的,默认不会触发你写的本地监听器,你现在的监听器能触发本质是本地事件调度的同步执行,和Redis没有关系
解决方案:基于Redis发布订阅实现全节点广播
步骤1:改造事件发布逻辑
你可以保留原有Event类的结构,废弃ShouldBroadcast接口和原有队列配置,直接调用Redis原生发布能力推送事件:
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Redis; class BaseRedisEvent { use Dispatchable, SerializesModels; public string $channel = 'default'; // 自定义事件广播方法 public function publish() { Redis::publish($this->channel, serialize($this)); } }
发布事件时调用BaseRedisEvent::dispatch()->publish()即可。
步骤2:编写Redis订阅常驻命令
所有服务器都需要运行这个订阅命令,监听Redis频道的广播消息,触发本地处理逻辑:
- 生成命令文件
php artisan make:command RedisSubCommand - 编写命令逻辑
<?php namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Log; class RedisSubCommand extends Command { protected $signature = 'redis:sub'; protected $description = '订阅Redis全局广播事件'; public function handle() { // 可传入多个频道名监听多类事件 Redis::subscribe(['default'], function ($message) { $event = unserialize($message); // 此处执行你原有Listener的处理逻辑即可 Log::debug('New Event in Redis ' . var_export($event, true)); // 如果要兼容原有Listener绑定逻辑,直接调用event($event)即可触发本地对应监听器 }); } }
步骤3:配置进程守护
所有服务器都需要用Supervisor守护订阅进程,保证进程常驻、崩溃自动重启,Supervisor配置示例:
[program:laravel-redis-sub] process_name=%(program_name)s_%(process_num)02d command=php /你的项目绝对路径/artisan redis:sub autostart=true autorestart=true user=www-data numprocs=1 redirect_stderr=true stdout_logfile=/你的项目绝对路径/storage/logs/redis-sub.log stopwaitsecs=3600
配置完成后更新Supervisor配置启动进程,所有服务器的订阅进程启动完成后,任意节点发布事件,全部服务器都会收到消息执行对应逻辑。
可选优化
如果业务要求不能丢失事件消息,可将Redis Pub/Sub替换为Redis Stream模式,给每个服务器分配独立的消费组,各自消费全量消息即可避免进程挂掉期间的消息丢失。
内容的提问来源于stack exchange,提问作者PP ZAQ1
相关产品推荐
相关产品推荐

