🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
<article><h1>Laravel 的队列系统介绍</h1><ul><li><a href="#introduction">简介</a><ul><li><a href="#connections-vs-queues">连接 Vs. 队列</a></li><li><a href="#driver-prerequisites">驱动的必要设置</a></li></ul></li><li><a href="#creating-jobs">创建任务类</a><ul><li><a href="#generating-job-classes">生成任务类</a></li><li><a href="#class-structure">任务类结构</a></li></ul></li><li><a href="#dispatching-jobs">分发任务</a><ul><li><a href="#delayed-dispatching">延迟分发</a></li><li><a href="#customizing-the-queue-and-connection">自定义队列 &amp; 连接</a></li><li><a href="#max-job-attempts-and-timeout">指定任务最大尝试次数 / 超时值</a></li><li><a href="#error-handling">错误处理</a></li></ul></li><li><a href="#running-the-queue-worker">运行队列处理器</a><ul><li><a href="#queue-priorities">队列优先级</a></li><li><a href="#queue-workers-and-deployment">队列处理器 &amp; 部署</a></li><li><a href="#job-expirations-and-timeouts">任务过期 &amp; 超时</a></li></ul></li><li><a href="#supervisor-configuration">Supervisor 配置</a></li><li><a href="#dealing-with-failed-jobs">处理失败的任务</a><ul><li><a href="#cleaning-up-after-failed-jobs">清除失败任务</a></li><li><a href="#failed-job-events">任务失败事件</a></li><li><a href="#retrying-failed-jobs">重试失败的任务</a></li></ul></li><li><a href="#job-events">任务事件</a></li></ul><p><a name="introduction"></a></p><h2><a href="#introduction">简介</a></h2><p>Laravel 队列为不同的后台队列服务提供统一的 API , 例如 Beanstalk,Amazon SQS, Redis,甚至其他基于关系型数据库的队列。 队列的目的是将耗时的任务延时处理,比如发送邮件,从而大幅度缩短Web请求和相应的时间。</p><p>队列配置文件存放在 <code class=" language-php">config<span class="token operator">/</span>queue<span class="token punctuation">.</span>php</code>。 每一种队列驱动的配置都可以在该文件中找到, 包括数据库, <a href="https://kr.github.io/beanstalkd/">Beanstalkd</a>, <a href="https://aws.amazon.com/sqs/">Amazon SQS</a>, <a href="http://redis.io">Redis</a>, 以及同步(本地使用)驱动。 其中还包含了一个<code class=" language-php"><span class="token keyword">null</span></code>队列驱动用于那些放弃队列的任务。</p><p><a name="connections-vs-queues"></a></p><h3>连接 Vs. 队列</h3><p>在开始使用 Laravel 队列前,弄明白 「连接」 和 「队列」 的区别是很重要的。在你的 <code class=" language-php">config<span class="token operator">/</span>queue<span class="token punctuation">.</span>php</code> 配置文件里, 有一个 <code class=" language-php">connections</code> 配置选项。 这个选项给 Amazon SQS, Beanstalk ,或者 Redis 这样的后端服务定义了一个特有的连接。不管是哪一种,一个给定的连接可能会有多个「队列」,而 「队列」可以被认为是不同的栈或者大量的队列任务。</p><p>要注意的是, <code class=" language-php">queue</code> 配置文件中每个连接的配置示例中都包含一个 <code class=" language-php">queue</code> 属性。这是默认队列,任务被发给指定连接的时候会被分发到这个队列中。换句话说,如果你分发任务的时候没有显式定义队列,那么它就会被放到连接配置中 <code class=" language-php">queue</code> 属性所定义的队列中:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">// 这个任务将被分发到默认队列... </span><span class="token function">dispatch<span class="token punctuation">(</span></span><span class="token keyword">new</span> <span class="token class-name">Job</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true"> // 这个任务将被发送到「emails」队列... </span><span class="token function">dispatch<span class="token punctuation">(</span></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Job</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">onQueue<span class="token punctuation">(</span></span><span class="token string">'emails'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>有些应用可能不需要把任务发到不同的队列,而只发到一个简单的队列中就行了。但是把任务推到不同的队列仍然是非常有用的,因为 Laravel 队列处理器允许你定义队列的优先级,所以你能给不同的队列划分不同的优先级或者区分不同任务的不同处理方式了。比如说,如果你把任务推到 <code class=" language-php">high</code> 队列中,你就能让队列处理器优先处理这些任务了:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work <span class="token operator">--</span>queue<span class="token operator">=</span>high<span class="token punctuation">,</span><span class="token keyword">default</span></code></pre><p><a name="driver-prerequisites"></a></p><h3>驱动的必要设置</h3><h4>数据库</h4><p>要使用 <code class=" language-php">database</code> 这个队列驱动的话, 你需要创建一个数据表来存储任务,你可以用 <code class=" language-php">queue<span class="token punctuation">:</span>table</code> 这个 Artisan 命令来创建这个数据表的迁移。 当迁移创建好以后,就可以用 <code class=" language-php">migrate</code> 这条命令来创建数据表:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>table php artisan migrate</code></pre><h4>Redis</h4><p>为了使用 <code class=" language-php">redis</code> 队列驱动, 你需要在你的配置文件 <code class=" language-php">config<span class="token operator">/</span>database<span class="token punctuation">.</span>php</code> 中配置Redis的数据库连接</p><p>如果你的 Redis 队列连接使用的是 Redis 集群, 你的队列名称必须包含 <a href="https://redis.io/topics/cluster-spec#keys-hash-tags">key hash tag</a> 。 这是为了确保所有的 redis 键对于一个给定的队列都置于同一哈希中:</p><pre class=" language-php"><code class=" language-php"><span class="token string">'redis'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span> <span class="token string">'driver'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'redis'</span><span class="token punctuation">,</span> <span class="token string">'connection'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'default'</span><span class="token punctuation">,</span> <span class="token string">'queue'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'{default}'</span><span class="token punctuation">,</span> <span class="token string">'retry_after'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">90</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span></code></pre><h4>其它队列驱动的依赖扩展包</h4><p>在使用列表里的队列服务前,必须安装以下依赖扩展包:</p><div class="content-list"><ul><li>Amazon SQS: <code class=" language-php">aws<span class="token operator">/</span>aws<span class="token operator">-</span>sdk<span class="token operator">-</span>php <span class="token operator">~</span><span class="token number">3.0</span></code></li><li>Beanstalkd: <code class=" language-php">pda<span class="token operator">/</span>pheanstalk <span class="token operator">~</span><span class="token number">3.0</span></code></li><li>Redis: <code class=" language-php">predis<span class="token operator">/</span>predis <span class="token operator">~</span><span class="token number">1.0</span></code></li></ul></div><p><a name="creating-jobs"></a></p><h2><a href="#creating-jobs">创建任务</a></h2><p><a name="generating-job-classes"></a></p><h3>生成任务类</h3><p>在你的应用程序中,队列的任务类都默认放在 <code class=" language-php">app<span class="token operator">/</span>Jobs</code> 目录下,如果这个目录不存在,那当你运行 <code class=" language-php">make<span class="token punctuation">:</span>job</code> artisan 命令时目录就会被自动创建。 你可以用以下的 Artisan 命令来生成一个新的队列任务:</p><pre class=" language-php"><code class=" language-php">php artisan make<span class="token punctuation">:</span>job SendReminderEmail</code></pre><p>生成的类实现了 <code class=" language-php">Illuminate\<span class="token package">Contracts<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>ShouldQueue</span></code> 接口,这意味着这个任务将会被推送到队列中,而不是同步执行。</p><p><a name="class-structure"></a></p><h3>任务类结构</h3><p>任务类的结构很简单,一般来说只会包含一个让队列用来调用此任务的 <code class=" language-php">handle</code> 方法。我们来看一个示例的任务类,这个示例里,假设我们管理着一个播客发布服务,在发布之前需要处理上传播客文件:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Jobs</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Podcast</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>AudioProcessor</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Bus<span class="token punctuation">\</span>Queueable</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>SerializesModels</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>InteractsWithQueue</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Contracts<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>ShouldQueue</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">ProcessPodcast</span> <span class="token keyword">implements</span> <span class="token class-name">ShouldQueue</span> <span class="token punctuation">{</span> <span class="token keyword">use</span> <span class="token package">InteractsWithQueue</span><span class="token punctuation">,</span> Queueable<span class="token punctuation">,</span> SerializesModels<span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token variable">$podcast</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** * 创建一个新的任务实例。 * * @param Podcast $podcast * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct<span class="token punctuation">(</span></span>Podcast <span class="token variable">$podcast</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">podcast</span> <span class="token operator">=</span> <span class="token variable">$podcast</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 运行任务。 * * @param AudioProcessor $processor * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">handle<span class="token punctuation">(</span></span>AudioProcessor <span class="token variable">$processor</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // Process uploaded podcast... </span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>注意,在这个例子中,我们在任务类的构造器中直接传递了一个 <a href="/docs/5.4/eloquent">Eloquent 模型</a>。因为我们在任务类里引用了 <code class=" language-php">SerializesModels</code> 这个 ,使得 Eloquent 模型在处理任务时可以被优雅地序列化和反序列化。如果你的队列任务类在构造器中接收了一个 Eloquent 模型,那么只有可识别出该模型的属性会被序列化到队列里。当任务被实际运行时,队列系统便会自动从数据库中重新取回完整的模型。这整个过程对你的应用程序来说是完全透明的,这样可以避免在序列化完整的 Eloquent 模式实例时所带来的一些问题。</p><p>在队列处理任务时,会调用 <code class=" language-php">handle</code> 方法,而这里我们也可以通过 handle 方法的参数类型提示,让 Laravel 的 <a href="/docs/5.4/container">服务容器</a> 自动注入依赖对象。</p><blockquote class="has-icon note"><p><div class="flag"><span class="svg"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1.1" x="0px" y="0px" width="90px" height="90px" viewBox="0 0 90 90" enable-background="new 0 0 90 90" xml:space="preserve"><path fill="#FFFFFF" d="M45 0C20.1 0 0 20.1 0 45s20.1 45 45 45 45-20.1 45-45S69.9 0 45 0zM45 74.5c-3.6 0-6.5-2.9-6.5-6.5s2.9-6.5 6.5-6.5 6.5 2.9 6.5 6.5S48.6 74.5 45 74.5zM52.1 23.9l-2.5 29.6c0 2.5-2.1 4.6-4.6 4.6 -2.5 0-4.6-2.1-4.6-4.6l-2.5-29.6c-0.1-0.4-0.1-0.7-0.1-1.1 0-4 3.2-7.2 7.2-7.2 4 0 7.2 3.2 7.2 7.2C52.2 23.1 52.2 23.5 52.1 23.9z"></path></svg></span></div> 像图片内容这种二进制数据, 在放入队列任务之前必须使用 <code class=" language-php">base64_encode</code> 方法转换一下。 否则,当这项任务放置到队列中时,可能无法正确序列化为 JSON。</p></blockquote><p><a name="dispatching-jobs"></a></p><h2><a href="#dispatching-jobs">分发任务</a></h2><p>你写好任务类后,就能通过 <code class=" language-php">dispatch</code> 辅助函数来分发它了。唯一需要传递给 <code class=" language-php">dispatch</code> 的参数是这个任务类的实例:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Controllers</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Jobs<span class="token punctuation">\</span>ProcessPodcast</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Controllers<span class="token punctuation">\</span>Controller</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">PodcastController</span> <span class="token keyword">extends</span> <span class="token class-name">Controller</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 保存播客。 * * @param Request $request * @return Response */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">store<span class="token punctuation">(</span></span>Request <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // 创建播客... </span> <span class="token function">dispatch<span class="token punctuation">(</span></span><span class="token keyword">new</span> <span class="token class-name">ProcessPodcast</span><span class="token punctuation">(</span><span class="token variable">$podcast</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><blockquote class="has-icon tip"><p><div class="flag"><span class="svg"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1.1" x="0px" y="0px" width="56.6px" height="87.5px" viewBox="0 0 56.6 87.5" enable-background="new 0 0 56.6 87.5" xml:space="preserve"><path fill="#FFFFFF" d="M28.7 64.5c-1.4 0-2.5-1.1-2.5-2.5v-5.7 -5V41c0-1.4 1.1-2.5 2.5-2.5s2.5 1.1 2.5 2.5v10.1 5 5.8C31.2 63.4 30.1 64.5 28.7 64.5zM26.4 0.1C11.9 1 0.3 13.1 0 27.7c-0.1 7.9 3 15.2 8.2 20.4 0.5 0.5 0.8 1 1 1.7l3.1 13.1c0.3 1.1 1.3 1.9 2.4 1.9 0.3 0 0.7-0.1 1.1-0.2 1.1-0.5 1.6-1.8 1.4-3l-2-8.4 -0.4-1.8c-0.7-2.9-2-5.7-4-8 -1-1.2-2-2.5-2.7-3.9C5.8 35.3 4.7 30.3 5.4 25 6.7 14.5 15.2 6.3 25.6 5.1c13.9-1.5 25.8 9.4 25.8 23 0 4.1-1.1 7.9-2.9 11.2 -0.8 1.4-1.7 2.7-2.7 3.9 -2 2.3-3.3 5-4 8L41.4 53l-2 8.4c-0.3 1.2 0.3 2.5 1.4 3 0.3 0.2 0.7 0.2 1.1 0.2 1.1 0 2.2-0.8 2.4-1.9l3.1-13.1c0.2-0.6 0.5-1.2 1-1.7 5-5.1 8.2-12.1 8.2-19.8C56.4 12 42.8-1 26.4 0.1zM43.7 69.6c0 0.5-0.1 0.9-0.3 1.3 -0.4 0.8-0.7 1.6-0.9 2.5 -0.7 3-2 8.6-2 8.6 -1.3 3.2-4.4 5.5-7.9 5.5h-4.1H28h-0.5 -3.6c-3.5 0-6.7-2.4-7.9-5.7l-0.1-0.4 -1.8-7.8c-0.4-1.1-0.8-2.1-1.2-3.1 -0.1-0.3-0.2-0.5-0.2-0.9 0.1-1.3 1.3-2.1 2.6-2.1H41C42.4 67.5 43.6 68.2 43.7 69.6zM37.7 72.5H26.9c-4.2 0-7.2 3.9-6.3 7.9 0.6 1.3 1.8 2.1 3.2 2.1h4.1 0.5 0.5 3.6c1.4 0 2.7-0.8 3.2-2.1L37.7 72.5z"></path></svg></span></div> <code class=" language-php">dispatch</code> 提供了一种简捷、全局可用的函数,它也非常容易测试。查看下 Laravel <a href="/docs/5.4/testing">测试文档</a> 来了解更多。</p></blockquote><p><a name="delayed-dispatching"></a></p><h3>延迟分发</h3><p>如果你想延迟执行一个队列中的任务,你可以用任务实例的 <code class=" language-php">delay</code> 方法。 这个方法是 <code class=" language-php">Illuminate\<span class="token package">Bus<span class="token punctuation">\</span>Queueable</span></code> trait 提供的,而这个 trait 在所有自动生成的任务类中都是默认加载了的。对于延迟任务我们可以举个例子,比如指定一个被分发10分钟后才执行的任务:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Controllers</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Carbon<span class="token punctuation">\</span>Carbon</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Jobs<span class="token punctuation">\</span>ProcessPodcast</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Controllers<span class="token punctuation">\</span>Controller</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">PodcastController</span> <span class="token keyword">extends</span> <span class="token class-name">Controller</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 保存一个新的播客。 * * @param Request $request * @return Response */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">store<span class="token punctuation">(</span></span>Request <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // 创建播客... </span> <span class="token variable">$job</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ProcessPodcast</span><span class="token punctuation">(</span><span class="token variable">$podcast</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">delay<span class="token punctuation">(</span></span><span class="token scope">Carbon<span class="token punctuation">::</span></span><span class="token function">now<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">addMinutes<span class="token punctuation">(</span></span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">dispatch<span class="token punctuation">(</span></span><span class="token variable">$job</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><blockquote class="has-icon note"><p><div class="flag"><span class="svg"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1.1" x="0px" y="0px" width="90px" height="90px" viewBox="0 0 90 90" enable-background="new 0 0 90 90" xml:space="preserve"><path fill="#FFFFFF" d="M45 0C20.1 0 0 20.1 0 45s20.1 45 45 45 45-20.1 45-45S69.9 0 45 0zM45 74.5c-3.6 0-6.5-2.9-6.5-6.5s2.9-6.5 6.5-6.5 6.5 2.9 6.5 6.5S48.6 74.5 45 74.5zM52.1 23.9l-2.5 29.6c0 2.5-2.1 4.6-4.6 4.6 -2.5 0-4.6-2.1-4.6-4.6l-2.5-29.6c-0.1-0.4-0.1-0.7-0.1-1.1 0-4 3.2-7.2 7.2-7.2 4 0 7.2 3.2 7.2 7.2C52.2 23.1 52.2 23.5 52.1 23.9z"></path></svg></span></div> Amazon SQS 队列服务最大延迟 15 分钟。</p></blockquote><p><a name="customizing-the-queue-and-connection"></a></p><h3>自定义队列 &amp; 连接</h3><h4>分发任务到指定队列</h4><p>通过推送任务到不同的队列,你可以给队列任务分类,甚至可以控制给不同的队列分配多少任务。记住,这个并不是要推送任务到队列配置文件中不同的 「connections」 里,而是推送到一个连接中不同的队列里。要指定队列的话,就调用任务实例的 <code class=" language-php">onQueue</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Controllers</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Jobs<span class="token punctuation">\</span>ProcessPodcast</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Controllers<span class="token punctuation">\</span>Controller</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">PodcastController</span> <span class="token keyword">extends</span> <span class="token class-name">Controller</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 保存一个新的播客。 * * @param Request $request * @return Response */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">store<span class="token punctuation">(</span></span>Request <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // 创建播客... </span> <span class="token variable">$job</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ProcessPodcast</span><span class="token punctuation">(</span><span class="token variable">$podcast</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">onQueue<span class="token punctuation">(</span></span><span class="token string">'processing'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">dispatch<span class="token punctuation">(</span></span><span class="token variable">$job</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><h4>分发任务到指定连接</h4><p>如果你使用了多个队列连接,你可以把任务推到指定连接。要指定连接的话,你可以调用任务实例的 <code class=" language-php">onConnection</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Controllers</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Jobs<span class="token punctuation">\</span>ProcessPodcast</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Controllers<span class="token punctuation">\</span>Controller</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">PodcastController</span> <span class="token keyword">extends</span> <span class="token class-name">Controller</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 保存一个新的播客。 * * @param Request $request * @return Response */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">store<span class="token punctuation">(</span></span>Request <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // 创建播客... </span> <span class="token variable">$job</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ProcessPodcast</span><span class="token punctuation">(</span><span class="token variable">$podcast</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">onConnection<span class="token punctuation">(</span></span><span class="token string">'sqs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">dispatch<span class="token punctuation">(</span></span><span class="token variable">$job</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>当然,你可以链式调用 <code class=" language-php">onConnection</code> 和 <code class=" language-php">onQueue</code> 来同时指定任务的连接和队列:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$job</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ProcessPodcast</span><span class="token punctuation">(</span><span class="token variable">$podcast</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">onConnection<span class="token punctuation">(</span></span><span class="token string">'sqs'</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">onQueue<span class="token punctuation">(</span></span><span class="token string">'processing'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="max-job-attempts-and-timeout"></a></p><h3>指定任务最大尝试次数 / 超时值</h3><h4>最大尝试次数</h4><p>在一项任务中指定最大的尝试次数可以尝试通过 Artisan 命令行 <code class=" language-php"><span class="token operator">--</span>tries</code> 来设置:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work <span class="token operator">--</span>tries<span class="token operator">=</span><span class="token number">3</span></code></pre><p>但是,你可以采取更为精致的方法来完成这项工作比如说在任务类中定义最大尝试次数。如果在类和命令行中都定义了最大尝试次数, Laravel 会优先执行任务类中的值:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Jobs</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">ProcessPodcast</span> <span class="token keyword">implements</span> <span class="token class-name">ShouldQueue</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 任务最大尝试次数 * * @var int */</span> <span class="token keyword">public</span> <span class="token variable">$tries</span> <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><h4>超时</h4><p>同样的,任务可以运行的最大秒数可以使用 Artisan 命令行上的 <code class=" language-php"><span class="token operator">--</span>timeout</code> 开关指定:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work <span class="token operator">--</span>timeout<span class="token operator">=</span><span class="token number">30</span></code></pre><p>然而,你也可以在任务类中定义一个变量来设置可运行的最大描述,如果在类和命令行中都定义了最大尝试次数, Laravel 会优先执行命令行中的值:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Jobs</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">ProcessPodcast</span> <span class="token keyword">implements</span> <span class="token class-name">ShouldQueue</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 任务运行的超时时间。 * * @var int */</span> <span class="token keyword">public</span> <span class="token variable">$timeout</span> <span class="token operator">=</span> <span class="token number">120</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p><a name="error-handling"></a></p><h3>错误处理</h3><p>如果任务运行的时候抛出异常,这个任务就自动被释放回队列,这样它就能被再重新运行了。如果继续抛出异常,这个任务会继续被释放回队列,直到重试次数达到你应用允许的最多次数。这个最多次数是在调用 <code class=" language-php">queue<span class="token punctuation">:</span>work</code> Artisan 命令的时候通过 <code class=" language-php"><span class="token operator">--</span>tries</code> 参数来定义的。更多队列处理器的信息可以 <a href="#running-the-queue-worker">在下面看到</a> 。</p><p><a name="running-the-queue-worker"></a></p><h2><a href="#running-the-queue-worker">运行队列处理器</a></h2><p>Laravel 包含一个队列处理器,当新任务被推到队列中时它能处理这些任务。你可以通过 queue:work Artisan 命令来运行处理器。要注意,一旦 <code class=" language-php">queue<span class="token punctuation">:</span>work</code> 命令开始,它将一直运行,直到你手动停止或者你关闭控制台:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work</code></pre><blockquote class="has-icon tip"><p><div class="flag"><span class="svg"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1.1" x="0px" y="0px" width="56.6px" height="87.5px" viewBox="0 0 56.6 87.5" enable-background="new 0 0 56.6 87.5" xml:space="preserve"><path fill="#FFFFFF" d="M28.7 64.5c-1.4 0-2.5-1.1-2.5-2.5v-5.7 -5V41c0-1.4 1.1-2.5 2.5-2.5s2.5 1.1 2.5 2.5v10.1 5 5.8C31.2 63.4 30.1 64.5 28.7 64.5zM26.4 0.1C11.9 1 0.3 13.1 0 27.7c-0.1 7.9 3 15.2 8.2 20.4 0.5 0.5 0.8 1 1 1.7l3.1 13.1c0.3 1.1 1.3 1.9 2.4 1.9 0.3 0 0.7-0.1 1.1-0.2 1.1-0.5 1.6-1.8 1.4-3l-2-8.4 -0.4-1.8c-0.7-2.9-2-5.7-4-8 -1-1.2-2-2.5-2.7-3.9C5.8 35.3 4.7 30.3 5.4 25 6.7 14.5 15.2 6.3 25.6 5.1c13.9-1.5 25.8 9.4 25.8 23 0 4.1-1.1 7.9-2.9 11.2 -0.8 1.4-1.7 2.7-2.7 3.9 -2 2.3-3.3 5-4 8L41.4 53l-2 8.4c-0.3 1.2 0.3 2.5 1.4 3 0.3 0.2 0.7 0.2 1.1 0.2 1.1 0 2.2-0.8 2.4-1.9l3.1-13.1c0.2-0.6 0.5-1.2 1-1.7 5-5.1 8.2-12.1 8.2-19.8C56.4 12 42.8-1 26.4 0.1zM43.7 69.6c0 0.5-0.1 0.9-0.3 1.3 -0.4 0.8-0.7 1.6-0.9 2.5 -0.7 3-2 8.6-2 8.6 -1.3 3.2-4.4 5.5-7.9 5.5h-4.1H28h-0.5 -3.6c-3.5 0-6.7-2.4-7.9-5.7l-0.1-0.4 -1.8-7.8c-0.4-1.1-0.8-2.1-1.2-3.1 -0.1-0.3-0.2-0.5-0.2-0.9 0.1-1.3 1.3-2.1 2.6-2.1H41C42.4 67.5 43.6 68.2 43.7 69.6zM37.7 72.5H26.9c-4.2 0-7.2 3.9-6.3 7.9 0.6 1.3 1.8 2.1 3.2 2.1h4.1 0.5 0.5 3.6c1.4 0 2.7-0.8 3.2-2.1L37.7 72.5z"></path></svg></span></div> 要让 <code class=" language-php">queue<span class="token punctuation">:</span>work</code> 进程永久在后台运行,你应该使用进程监控工具,比如 <code class=" language-php">Supervisor</code> 来保证队列处理器没有停止运行。</p></blockquote><p>一定要记得,队列处理器是长时间运行的进程,并在内存里保存着已经启动的应用状态。这样的结果就是,处理器运行后如果你修改代码那这些改变是不会应用到处理器中的。所以在你重新部署过程中,一定要 <a href="#queue-workers-and-deployment">重启队列处理器</a> 。</p><h4>指定连接 &amp; 队列</h4><p>你可以指定队列处理器所使用的连接。你在 <code class=" language-php">config<span class="token operator">/</span>queue<span class="token punctuation">.</span>php</code> 配置文件里定义了多个连接,而你传递给 <code class=" language-php">work</code> 命令的连接名字要至少跟它们其中一个是一致的:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work redis</code></pre><p>你可以自定义队列处理器,方式是处理给定连接的特定队列。举例来说,如果你所有的邮件都是在 <code class=" language-php">redis</code> 连接中的 <code class=" language-php">emails</code> 队列中处理的,你就能通过以下命令启动一个只处理那个特定队列的队列处理器了:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work redis <span class="token operator">--</span>queue<span class="token operator">=</span>emails</code></pre><h4>资源注意事项</h4><p>守护程序队列不会在处理每个作业之前 「重新启动」 框架。 因此,在每个任务完成后,您应该释放任何占用过大的资源。例如,如果你使用GD库进行图像处理,你应该在完成后用 <code class=" language-php">imagedestroy</code> 释放内存。</p><p><a name="queue-priorities"></a></p><h3>队列优先级</h3><p>有时候你希望设置处理队列的优先级。比如在 <code class=" language-php">config<span class="token operator">/</span>queue<span class="token punctuation">.</span>php</code> 里你可能设置了 <code class=" language-php">redis</code> 连接中的默认队列优先级为 <code class=" language-php">low</code>,但是你可能偶尔希望把一个任务推到 <code class=" language-php">high</code> 优先级的队列中,像这样:</p><pre class=" language-php"><code class=" language-php"><span class="token function">dispatch<span class="token punctuation">(</span></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Job</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">onQueue<span class="token punctuation">(</span></span><span class="token string">'high'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>要验证 <code class=" language-php">high</code> 队列中的任务都是在 <code class=" language-php">low</code> 队列中的任务之前处理的,你要启动一个队列处理器,传递给它队列名字的列表并以英文逗号,间隔:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work <span class="token operator">--</span>queue<span class="token operator">=</span>high<span class="token punctuation">,</span>low</code></pre><p><a name="queue-workers-and-deployment"></a></p><h3>队列处理器 &amp; 部署</h3><p>因为队列处理器都是 long-lived 进程,如果代码改变而队列处理器没有重启,他们是不能应用新代码的。所以最简单的方式就是重新部署过程中要重启队列处理器。你可以很优雅地只输入 <code class=" language-php">queue<span class="token punctuation">:</span>restart</code> 来重启所有队列处理器。</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>restart</code></pre><p>这个命令将会告诉所有队列处理器在执行完当前任务后结束进程,这样才不会有任务丢失。因为队列处理器在执行 <code class=" language-php">queue<span class="token punctuation">:</span>restart</code> 命令时对结束进程,你应该运行一个进程管理器,比如 <a href="#supervisor-configuration">Supervisor</a> 来自动重新启动队列处理器。</p><p><a name="job-expirations-and-timeouts"></a></p><h3>任务过期 &amp; 超时</h3><h4>任务过期</h4><p><code class=" language-php">config<span class="token operator">/</span>queue<span class="token punctuation">.</span>php</code> 配置文件里,每一个队列连接都定义了一个 <code class=" language-php">retry_after</code> 选项。这个选项指定了任务最多处理多少秒后就被当做失败重试了。比如说,如果这个选项设置为 <code class=" language-php"><span class="token number">90</span></code>,那么当这个任务持续执行了 <code class=" language-php"><span class="token number">90</span></code> 秒而没有被删除,那么它将被释放回队列。通常情况下,你应该把 <code class=" language-php">retry_after</code> 设置为最长耗时的任务所对应的时间。</p><blockquote class="has-icon note"><p><div class="flag"><span class="svg"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1.1" x="0px" y="0px" width="90px" height="90px" viewBox="0 0 90 90" enable-background="new 0 0 90 90" xml:space="preserve"><path fill="#FFFFFF" d="M45 0C20.1 0 0 20.1 0 45s20.1 45 45 45 45-20.1 45-45S69.9 0 45 0zM45 74.5c-3.6 0-6.5-2.9-6.5-6.5s2.9-6.5 6.5-6.5 6.5 2.9 6.5 6.5S48.6 74.5 45 74.5zM52.1 23.9l-2.5 29.6c0 2.5-2.1 4.6-4.6 4.6 -2.5 0-4.6-2.1-4.6-4.6l-2.5-29.6c-0.1-0.4-0.1-0.7-0.1-1.1 0-4 3.2-7.2 7.2-7.2 4 0 7.2 3.2 7.2 7.2C52.2 23.1 52.2 23.5 52.1 23.9z"></path></svg></span></div> 唯一没有 <code class=" language-php">retry_after</code> 选项的连接是 Amazon SQS。当用 Amazon SQS 时,你必须通过 <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html">Amazon</a> 命令行来配置这个重试阈值。</p></blockquote><h4>队列处理器超时</h4><p><code class=" language-php">queue<span class="token punctuation">:</span>work</code> Artisan 命令对外有一个 <code class=" language-php"><span class="token operator">--</span>timeout</code> 选项。这个选项指定了 <code class=" language-php">Laravel</code> 队列处理器最多执行多长时间后就应该被关闭掉。有时候一个队列的子进程会因为很多原因僵死,比如一个外部的 HTTP 请求没有响应。这个 <code class=" language-php"><span class="token operator">--</span>timeout</code> 选项会移除超出指定事件限制的僵死进程。</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work <span class="token operator">--</span>timeout<span class="token operator">=</span><span class="token number">60</span></code></pre><p><code class=" language-php">retry_after</code> 配置选项和 <code class=" language-php"><span class="token operator">--</span>timeout</code> 命令行选项是不一样的,但是可以同时工作来保证任务不会丢失并且不会重复执行。</p><blockquote class="has-icon note"><p><div class="flag"><span class="svg"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1.1" x="0px" y="0px" width="90px" height="90px" viewBox="0 0 90 90" enable-background="new 0 0 90 90" xml:space="preserve"><path fill="#FFFFFF" d="M45 0C20.1 0 0 20.1 0 45s20.1 45 45 45 45-20.1 45-45S69.9 0 45 0zM45 74.5c-3.6 0-6.5-2.9-6.5-6.5s2.9-6.5 6.5-6.5 6.5 2.9 6.5 6.5S48.6 74.5 45 74.5zM52.1 23.9l-2.5 29.6c0 2.5-2.1 4.6-4.6 4.6 -2.5 0-4.6-2.1-4.6-4.6l-2.5-29.6c-0.1-0.4-0.1-0.7-0.1-1.1 0-4 3.2-7.2 7.2-7.2 4 0 7.2 3.2 7.2 7.2C52.2 23.1 52.2 23.5 52.1 23.9z"></path></svg></span></div> <code class=" language-php"><span class="token operator">--</span>timeout</code> 应该永远都要比 <code class=" language-php">retry_after</code> 短至少几秒钟的时间。这样就能保证任务进程总能在失败重试前就被杀死了。如果你的 <code class=" language-php"><span class="token operator">--</span>timeout</code> 选项大于 <code class=" language-php">retry_after</code> 配置选项,你的任务可能被执行两次。</p></blockquote><h4>队列进程睡眠时间</h4><p>当队列需要处理任务时,进程将继续处理任务,它们之间没有延迟。 但是,如果没有新的工作可用,<code class=" language-php">sleep</code> 参数决定了工作进程将「睡眠」多长时间:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work <span class="token operator">--</span>sleep<span class="token operator">=</span><span class="token number">3</span></code></pre><p><a name="supervisor-configuration"></a></p><h2><a href="#supervisor-configuration">Supervisor 配置</a></h2><h4>安装 Supervisor</h4><p>Supervisor 是一个 Linux 操作系统上的进程监控软件,它会在 <code class=" language-php">queue<span class="token punctuation">:</span>listen</code> 或 <code class=" language-php">queue<span class="token punctuation">:</span>work</code> 命令发生失败后自动重启它们。要在 Ubuntu 安装 Supervisor,可以用以下命令:</p><pre class=" language-php"><code class=" language-php">sudo apt<span class="token operator">-</span>get install supervisor</code></pre><blockquote class="has-icon tip"><p><div class="flag"><span class="svg"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1.1" x="0px" y="0px" width="56.6px" height="87.5px" viewBox="0 0 56.6 87.5" enable-background="new 0 0 56.6 87.5" xml:space="preserve"><path fill="#FFFFFF" d="M28.7 64.5c-1.4 0-2.5-1.1-2.5-2.5v-5.7 -5V41c0-1.4 1.1-2.5 2.5-2.5s2.5 1.1 2.5 2.5v10.1 5 5.8C31.2 63.4 30.1 64.5 28.7 64.5zM26.4 0.1C11.9 1 0.3 13.1 0 27.7c-0.1 7.9 3 15.2 8.2 20.4 0.5 0.5 0.8 1 1 1.7l3.1 13.1c0.3 1.1 1.3 1.9 2.4 1.9 0.3 0 0.7-0.1 1.1-0.2 1.1-0.5 1.6-1.8 1.4-3l-2-8.4 -0.4-1.8c-0.7-2.9-2-5.7-4-8 -1-1.2-2-2.5-2.7-3.9C5.8 35.3 4.7 30.3 5.4 25 6.7 14.5 15.2 6.3 25.6 5.1c13.9-1.5 25.8 9.4 25.8 23 0 4.1-1.1 7.9-2.9 11.2 -0.8 1.4-1.7 2.7-2.7 3.9 -2 2.3-3.3 5-4 8L41.4 53l-2 8.4c-0.3 1.2 0.3 2.5 1.4 3 0.3 0.2 0.7 0.2 1.1 0.2 1.1 0 2.2-0.8 2.4-1.9l3.1-13.1c0.2-0.6 0.5-1.2 1-1.7 5-5.1 8.2-12.1 8.2-19.8C56.4 12 42.8-1 26.4 0.1zM43.7 69.6c0 0.5-0.1 0.9-0.3 1.3 -0.4 0.8-0.7 1.6-0.9 2.5 -0.7 3-2 8.6-2 8.6 -1.3 3.2-4.4 5.5-7.9 5.5h-4.1H28h-0.5 -3.6c-3.5 0-6.7-2.4-7.9-5.7l-0.1-0.4 -1.8-7.8c-0.4-1.1-0.8-2.1-1.2-3.1 -0.1-0.3-0.2-0.5-0.2-0.9 0.1-1.3 1.3-2.1 2.6-2.1H41C42.4 67.5 43.6 68.2 43.7 69.6zM37.7 72.5H26.9c-4.2 0-7.2 3.9-6.3 7.9 0.6 1.3 1.8 2.1 3.2 2.1h4.1 0.5 0.5 3.6c1.4 0 2.7-0.8 3.2-2.1L37.7 72.5z"></path></svg></span></div> 如果自己手动配置 Supervisor 听起来有点难以应付,可以考虑使用 <a href="https://forge.laravel.com">Laravel Forge</a> ,它能给你的 Laravel 项目自动安装与配置 Supervisor。</p></blockquote><h4>配置 Supervisor</h4><p>Supervisor 的配置文件一般是放在 <code class=" language-php"><span class="token operator">/</span>etc<span class="token operator">/</span>supervisor<span class="token operator">/</span>conf<span class="token punctuation">.</span>d</code> 目录下,在这个目录中你可以创建任意数量的配置文件来要求 Supervisor 怎样监控你的进程。例如我们创建一个 <code class=" language-php">laravel<span class="token operator">-</span>worker<span class="token punctuation">.</span>conf</code> 来启动与监控一个 <code class=" language-php">queue<span class="token punctuation">:</span>work</code> 进程:</p><pre class=" language-php"><code class=" language-php"><span class="token punctuation">[</span>program<span class="token punctuation">:</span>laravel<span class="token operator">-</span>worker<span class="token punctuation">]</span> process_name<span class="token operator">=</span><span class="token operator">%</span><span class="token punctuation">(</span>program_name<span class="token punctuation">)</span>s_<span class="token operator">%</span><span class="token punctuation">(</span>process_num<span class="token punctuation">)</span>02d command<span class="token operator">=</span>php <span class="token operator">/</span>home<span class="token operator">/</span>forge<span class="token operator">/</span>app<span class="token punctuation">.</span>com<span class="token operator">/</span>artisan queue<span class="token punctuation">:</span>work sqs <span class="token operator">--</span>sleep<span class="token operator">=</span><span class="token number">3</span> <span class="token operator">--</span>tries<span class="token operator">=</span><span class="token number">3</span> autostart<span class="token operator">=</span><span class="token boolean">true</span> autorestart<span class="token operator">=</span><span class="token boolean">true</span> user<span class="token operator">=</span>forge numprocs<span class="token operator">=</span><span class="token number">8</span> redirect_stderr<span class="token operator">=</span><span class="token boolean">true</span> stdout_logfile<span class="token operator">=</span><span class="token operator">/</span>home<span class="token operator">/</span>forge<span class="token operator">/</span>app<span class="token punctuation">.</span>com<span class="token operator">/</span>worker<span class="token punctuation">.</span>log</code></pre><p>这个例子里的 <code class=" language-php">numprocs</code> 命令会要求 Supervisor 运行并监控 8 个 <code class=" language-php">queue<span class="token punctuation">:</span>work</code> 进程,并且在它们运行失败后重新启动。当然,你必须更改 <code class=" language-php">command</code> 命令的 <code class=" language-php">queue<span class="token punctuation">:</span>work sqs</code>,以显示你所选择的队列驱动。</p><h4>启动 Supervisor</h4><p>当这个配置文件被创建后,你需要更新 Supervisor 的配置,并用以下命令来启动该进程:</p><pre class=" language-php"><code class=" language-php">sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start laravel<span class="token operator">-</span>worker<span class="token punctuation">:</span><span class="token operator">*</span></code></pre><p>更多有关 Supervisor 的设置与使用,请参考 <a href="http://supervisord.org/index.html">Supervisor</a> 官方文档。</p><p><a name="dealing-with-failed-jobs"></a></p><h2><a href="#dealing-with-failed-jobs">处理失败的任务</a></h2><p>有时候你队列中的任务会失败。不要担心,本来事情就不会一帆风顺。 Laravel 内置了一个方便的方式来指定任务重试的最大次数。当任务超出这个重试次数后,它就会被插入到 <code class=" language-php">failed_jobs</code> 数据表里面。要创建 <code class=" language-php">failed_jobs</code> 表的话,你可以用 <code class=" language-php">queue<span class="token punctuation">:</span>failed<span class="token operator">-</span>table</code> 命令:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>failed<span class="token operator">-</span>table php artisan migrate</code></pre><p>然后运行队列处理器,在调用 <a href="#running-the-queue-worker">queue:work</a> 命令时你应该通过 <code class=" language-php"><span class="token operator">--</span>tries</code> 参数指定任务的最大重试次数。如果不指定,任务就会永久重试:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>work redis <span class="token operator">--</span>tries<span class="token operator">=</span><span class="token number">3</span></code></pre><p><a name="cleaning-up-after-failed-jobs"></a></p><h3>清除失败任务</h3><p>你可以在任务类里直接定义 <code class=" language-php">failed</code> 方法,它能在任务失败时运行任务的清除逻辑。这个地方用来发一条警告给用户或者重置任务执行的操作等再好不过了。导致任务失败的异常信息会被传递到 <code class=" language-php">failed</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Jobs</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Exception</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Podcast</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>AudioProcessor</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Bus<span class="token punctuation">\</span>Queueable</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>SerializesModels</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>InteractsWithQueue</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Contracts<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>ShouldQueue</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">ProcessPodcast</span> <span class="token keyword">implements</span> <span class="token class-name">ShouldQueue</span> <span class="token punctuation">{</span> <span class="token keyword">use</span> <span class="token package">InteractsWithQueue</span><span class="token punctuation">,</span> Queueable<span class="token punctuation">,</span> SerializesModels<span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token variable">$podcast</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** * 创建一个新的任务实例。 * * @param Podcast $podcast * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct<span class="token punctuation">(</span></span>Podcast <span class="token variable">$podcast</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">podcast</span> <span class="token operator">=</span> <span class="token variable">$podcast</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 执行任务。 * * @param AudioProcessor $processor * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">handle<span class="token punctuation">(</span></span>AudioProcessor <span class="token variable">$processor</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // 处理上传播客... </span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 要处理的失败任务。 * * @param Exception $exception * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">failed<span class="token punctuation">(</span></span>Exception <span class="token variable">$exception</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // 给用户发送失败通知,等等... </span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p><a name="failed-job-events"></a> 任务失败事件</p><p>如果你想注册一个当队列任务失败时会被调用的事件,则可以用 <code class=" language-php"><span class="token scope">Queue<span class="token punctuation">::</span></span>failing</code> 方法。这样你就有机会通过这个事件来用 e-mail 或 <a href="https://www.hipchat.com">HipChat</a> 通知你的团队。例如我们可以在 Laravel 内置的 <code class=" language-php">AppServiceProvider</code> 中对这个事件附加一个回调函数:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Providers</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Support<span class="token punctuation">\</span>Facades<span class="token punctuation">\</span>Queue</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>Events<span class="token punctuation">\</span>JobFailed</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Support<span class="token punctuation">\</span>ServiceProvider</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">AppServiceProvider</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceProvider</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 启动任意应用程序的服务。 * * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">boot<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token scope">Queue<span class="token punctuation">::</span></span><span class="token function">failing<span class="token punctuation">(</span></span><span class="token keyword">function</span> <span class="token punctuation">(</span>JobFailed <span class="token variable">$event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // $event-&gt;connectionName </span> <span class="token comment" spellcheck="true"> // $event-&gt;job </span> <span class="token comment" spellcheck="true"> // $event-&gt;exception </span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 注册服务提供者。 * * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">register<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // </span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p><a name="retrying-failed-jobs"></a></p><h3>重试失败任务</h3><p>要查看你在 <code class=" language-php">failed_jobs</code> 数据表中的所有失败任务,则可以用 <code class=" language-php">queue<span class="token punctuation">:</span>failed</code> 这个 Artisan 命令:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>failed</code></pre><p><code class=" language-php">queue<span class="token punctuation">:</span>failed</code> 命令会列出所有任务的 ID、连接、队列以及失败时间,任务 ID 可以被用在重试失败的任务上。例如要重试一个 ID 为 <code class=" language-php"><span class="token number">5</span></code> 的失败任务,其命令如下:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>retry <span class="token number">5</span></code></pre><p>要重试所有失败的任务,可以使用 <code class=" language-php">queue<span class="token punctuation">:</span>retry</code> 并使用 <code class=" language-php">all</code> 作为 ID:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>retry all</code></pre><p>如果你想删除掉一个失败任务,可以用 <code class=" language-php">queue<span class="token punctuation">:</span>forget</code> 命令:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>forget <span class="token number">5</span></code></pre><p><code class=" language-php">queue<span class="token punctuation">:</span>flush</code> 命令可以让你删除所有失败的任务:</p><pre class=" language-php"><code class=" language-php">php artisan queue<span class="token punctuation">:</span>flush</code></pre><p><a name="job-events"></a></p><h2><a href="#job-events">任务事件</a></h2><p>使用队列的 <code class=" language-php">before</code> 和 <code class=" language-php">after</code> 方法,你能指定任务处理前和处理后的回调处理。在这些回调里正是实现额外的日志记录或者增加统计数据的好时机。通常情况下,你应该在 <a href="/docs/5.4/providers">服务容器</a> 中调用这些方法。例如,我们使用 Laravel 中的 AppServiceProvider:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App<span class="token punctuation">\</span>Providers</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Support<span class="token punctuation">\</span>Facades<span class="token punctuation">\</span>Queue</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Support<span class="token punctuation">\</span>ServiceProvider</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>Events<span class="token punctuation">\</span>JobProcessed</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Queue<span class="token punctuation">\</span>Events<span class="token punctuation">\</span>JobProcessing</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">AppServiceProvider</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceProvider</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 启动任意服务。 * * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">boot<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token scope">Queue<span class="token punctuation">::</span></span><span class="token function">before<span class="token punctuation">(</span></span><span class="token keyword">function</span> <span class="token punctuation">(</span>JobProcessing <span class="token variable">$event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // $event-&gt;connectionName </span> <span class="token comment" spellcheck="true"> // $event-&gt;job </span> <span class="token comment" spellcheck="true"> // $event-&gt;job-&gt;payload() </span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token scope">Queue<span class="token punctuation">::</span></span><span class="token function">after<span class="token punctuation">(</span></span><span class="token keyword">function</span> <span class="token punctuation">(</span>JobProcessed <span class="token variable">$event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // $event-&gt;connectionName </span> <span class="token comment" spellcheck="true"> // $event-&gt;job </span> <span class="token comment" spellcheck="true"> // $event-&gt;job-&gt;payload() </span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 注册服务提供者。 * * @return void */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">register<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // </span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>在 <code class=" language-php">队列</code> <a href="/docs/5.4/facades">facade</a> 中使用 <code class=" language-php">looping</code> 方法,你可以尝试在队列获取任务之前执行指定的回调方法。举个例子,你可以用闭包来回滚之前已失败任务的事务。</p><pre class=" language-php"><code class=" language-php"><span class="token scope">Queue<span class="token punctuation">::</span></span><span class="token function">looping<span class="token punctuation">(</span></span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token scope">DB<span class="token punctuation">::</span></span><span class="token function">transactionLevel<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token scope">DB<span class="token punctuation">::</span></span><span class="token function">rollBack<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></article>