Redis不可用时,能否让Laravel Horizon不执行任务入队?
在Laravel Horizon中全局阻止Redis不可用时的任务入队
当然可以通过全局配置/扩展实现这个需求,不用逐个任务加try-catch,下面是两种实用方案:
方案一:自定义Redis队列驱动包装器
通过包装Laravel原生的Redis队列驱动,全局捕获连接异常,直接拦截任务入队操作:
- 创建自定义连接器类
app/Queue/Connectors/RedisConnector.php:
<?php namespace App\Queue\Connectors; use Illuminate\Queue\Connectors\RedisConnector as BaseRedisConnector; use Predis\Connection\ConnectionException; use Illuminate\Queue\RedisQueue; class RedisConnector extends BaseRedisConnector { public function connect(array $config) { try { return parent::connect($config); } catch (ConnectionException $e) { // 记录Redis连接失败日志 logger()->error('Redis连接异常,任务无法入队:' . $e->getMessage()); // 返回"哑"队列实例,所有入队操作直接返回失败 return new class($this->redis, $config) extends RedisQueue { public function push($job, $data = '', $queue = null) { return false; } public function later($delay, $job, $data = '', $queue = null) { return false; } public function pushOn($queue, $job, $data = '') { return false; } public function laterOn($queue, $delay, $job, $data = '') { return false; } }; } } }
- 在
AppServiceProvider的boot方法中注册自定义连接器:
<?php namespace App\Providers; use App\Queue\Connectors\RedisConnector; use Illuminate\Queue\QueueManager; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot() { $this->app->extend('queue', function (QueueManager $manager) { $manager->addConnector('redis', function () { return new RedisConnector($this->app['redis']); }); return $manager; }); } }
这个方案会在Redis连接失败时,让所有入队操作直接返回false,不会抛出异常,也不会将任务写入队列。
方案二:全局异常处理器拦截队列入队异常
如果只是想避免异常扩散,同时记录日志,可以在全局异常处理器中针对性拦截:
修改app/Exceptions/Handler.php:
<?php namespace App\Exceptions; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Predis\Connection\ConnectionException; class Handler extends ExceptionHandler { public function register() { $this->reportable(function (ConnectionException $e) { // 判断异常是否来自队列入队流程 foreach ($e->getTrace() as $frame) { if (str_contains($frame['class'] ?? '', 'Illuminate\Queue\RedisQueue')) { logger()->error('Redis不可用,任务入队失败:' . $e->getMessage()); // 阻止异常继续向上抛出 return false; } } }); } }
这种方式不会主动阻止任务入队尝试,但会捕获并屏蔽异常,适合只需要避免报错的场景。
注意事项
- 方案一中的"哑"队列实例需要覆盖所有入队相关方法,确保没有遗漏;
- 可以根据业务需求,在拦截时抛出自定义业务异常,方便上层代码处理入队失败的情况;
- 测试时可手动关闭Redis服务,验证拦截逻辑是否生效。
内容的提问来源于stack exchange,提问作者Сергей Жидков
相关产品推荐
相关产品推荐

