Laravel Horizon清空队列报需调用redis.replicate_commands()问题咨询
环境组件版本信息
- Horizon版本: 5.7
- Laravel版本: 8.40
- PHP版本: 7.4.22
- Redis驱动及版本: predis/phpredis 3.2.12 & predis 1.1
- 数据库驱动及版本: 未提供
问题描述
执行队列清空操作时触发如下报错:
ERR Error running script (call to f_281c23c60c642d42fdedcc1acc8adc7317dd75a5): @user_script:18: @user_script: 18: Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.
报错本质是Redis 3.2及以上版本的EVAL脚本安全限制:脚本中执行过非确定性命令(如SCAN)后,默认脚本复制模式下不允许再执行写命令,避免主从数据不一致。
复现步骤
执行以下Artisan命令即可触发:
php artisan horizon:clear --queue=supplier_feeds_queue
临时解决方案
当前可通过直接操作Redis删除对应队列Key:
redis-cli --scan --pattern *supplier_feeds_queue* | xargs redis-cli del
永久修复方案
不需要自行手动调用redis.replicate_commands(),可按以下两种方案处理:
方案1:升级Horizon版本
当前使用的Horizon 5.7属于存在该Bug的版本,官方已在后续5.x分支的补丁版本中修复了该问题,直接升级Horizon到5.x最新稳定版即可,该升级不会破坏现有功能兼容性。
方案2:重写队列清空逻辑
如果无法升级依赖,可通过继承重写Horizon的RedisQueue类修复:
- 新建
app/Queue/HorizonRedisQueue.php文件,代码如下:
<?php namespace App\Queue; use Laravel\Horizon\RedisQueue as BaseRedisQueue; class HorizonRedisQueue extends BaseRedisQueue { public function clear($queue) { // 原Lua脚本第一行新增redis.replicate_commands()调用 $lua = <<<'LUA' redis.replicate_commands() local cursor = "0" local deleted = 0 repeat local results = redis.call('SCAN', cursor, 'MATCH', ARGV[1], 'COUNT', 1000) cursor = results[1] local keys = results[2] if #keys > 0 then deleted = deleted + redis.call('DEL', unpack(keys)) end until cursor == "0" return deleted LUA; return $this->getConnection()->eval($lua, 0, $this->getQueue($queue).'*'); } }
- 在
app/Providers/AppServiceProvider.php的register方法中添加绑定,替换默认的RedisQueue实现:
$this->app->bind('horizon.queue', function () { return new \App\Queue\HorizonRedisQueue( $this->app['redis']->connection(config('horizon.connection')) ); });
修复后再执行php artisan horizon:clear命令就不会触发该报错。
内容的提问来源于stack exchange,提问作者David van der Tuijn
相关产品推荐
相关产品推荐

