Laravel Echo Redis订阅上下文报错:仅允许(P)SUBSCRIBE类命令
解决Redis订阅回调中发送Notification触发的Redis命令限制错误
这个错误的核心原因很明确:当Redis连接处于订阅(subscribe)模式时,该连接只能执行SUBSCRIBE/UNSUBSCRIBE/PING/QUIT这几个特定命令。你的Notification发送广播时,大概率是复用了当前处于订阅状态的Redis连接,执行了发布(publish)或者其他Redis操作,直接触发了Redis的上下文限制。
最稳妥的解决方案:用队列异步处理Notification发送
既然订阅回调是阻塞且有命令限制的,我们把发送通知的逻辑转移到异步队列任务中,让它在独立的进程里执行,避免占用当前的订阅Redis连接。
步骤如下:
- 创建一个队列任务:
php artisan make:job SendPresenceChannelNotification
- 在任务类里封装发送Notification的逻辑:
<?php namespace App\Jobs; use App\Models\User; use App\Notifications\ExampleNotification; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class SendPresenceChannelNotification implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $users; protected $a2; protected $data; /** * Create a new job instance. */ public function __construct($users, $a2, $data) { $this->users = $users; $this->a2 = $a2; $this->data = $data; } /** * Execute the job. */ public function handle(): void { Notification::send($this->users, new ExampleNotification($this->a2, $this->data)); } }
- 修改你的Artisan命令回调,只负责调度这个任务:
public function handle() { $this->info("Presence channel watcher was started!"); Redis::subscribe(['PresenceChannelUpdated'] , function ($message, $channel) { // 对比加入和离开用户的代码,得到$a1->users、$a2、$data // 调度异步任务,而不是直接发送Notification SendPresenceChannelNotification::dispatch($a1->users, $a2, $data); }); }
为什么这个方案有效?
队列任务会被推送到队列中,由队列 worker 进程独立执行——worker 进程会创建全新的Redis连接来处理广播操作,完全不会和当前处于订阅状态的Redis连接产生冲突,自然就避开了Redis的命令上下文限制。
另外,你提到在Services中能正常发送这个Notification,也是因为Services里的Redis连接是正常的非订阅状态,没有命令执行限制。
内容的提问来源于stack exchange,提问作者m3xTa1nes
相关产品推荐
相关产品推荐

