标签 laravel 下的文章

LaraveSupervisorDashbord

做了一个查看 Laravel 进程状态和异步脚本队列数量的小工具:https://github.com/suren1986/laravel-supervisor-dashboard。

截图

Laravel queue 重复执行脚本的问题

现象

有时跑一些很耗费时间的异步脚本,会在 artisan queue:listen 时通过 --timeout=3600 设置长一点的时间,以避免超时。
如果: 1. 使用 Redis 存放 queue 信息; 2. 跑了多个进程 artisan queue:listen --timeout=3600;3. 监听同一个 queue;4. Job 的时间超过 60 秒。就会发生同一个脚本被运行多次的情况。

原因

  • 在 Laravel 的框架中,通过 Redis 获取下一个 Job 是通过 Illuminate\RedisQueue 完成的。
  • 获取下个 Job 的函数是 public function pop()
  • 假设 queue 的名称为 default,Laravel 在获取到脚本后,会将它放入 default:reserved 中。 default:reserved 的结构是 sorted hash,被放入的 Job 会被赋予一个权值:time() + this->expire,默认的 this->expire = 60,Laravel 官方的配置中(config/queue.php),也默认了 60 秒。
  • 这个函数在从 Redis queue 中 pop 一个数据前,会将所有过期的 Job 放回到原来的 queue 中。
  • 过期 Job 的来源包括 default:reserved,获取所有 default:reserved 中,权值小于当前时间的 Job。
  • 于是,超过 60 秒没有执行完成的 Job 被放入了 default queue 中,等待下一次执行。

解决方案

解决方法很简单,将 config/queue.php,相应配置的 expire 设置为 null 即可。
因为 Illuminate\RedisQueue 中,如果 expire 为 null,是不会做将将所有过期的 Job 放回到原来的 queue 中的操作的。

其它

  • 这个问题在 5.4 版本还有:5.4 版本的代码略有调整,但是原理相同。
  • 如果 queue 的驱动是 Database,应该也有相同的问题。而且看了 5.2 版本的实现,还没有办法通过设置 expire 为 null 来规避。
  • expire 这个设置,和 queue 的 timeout 的配置,是冲突的。Laravel 在这点的设计上没有考虑清楚:让开发者设置 2 个可能互相矛盾的参数。而且,文档中介绍了 timeout,教开发者避免超时,没有介绍 expire,还给 expire 一个默认的 60 秒设置,让开发者掉坑。打算找个时间去提 issue~