<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">自定義隊列 & 連接</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">隊列處理器 & 部署</a></li><li><a href="#job-expirations-and-timeouts">任務過期 & 超時</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">></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">></span> <span class="token punctuation">[</span>
<span class="token string">'driver'</span> <span class="token operator">=</span><span class="token operator">></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">></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">></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">></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"><?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">></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"><?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"><?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">></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">></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>自定義隊列 & 連接</h3><h4>分發任務到指定隊列</h4><p>通過推送任務到不同的隊列,你可以給隊列任務分類,甚至可以控制給不同的隊列分配多少任務。記住,這個并不是要推送任務到隊列配置文件中不同的 「connections」 里,而是推送到一個連接中不同的隊列里。要指定隊列的話,就調用任務實例的 <code class=" language-php">onQueue</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter"><?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">></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"><?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">></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">></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">></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"><?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"><?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>指定連接 & 隊列</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">></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>隊列處理器 & 部署</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>任務過期 & 超時</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"><?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">></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"><?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->connectionName
</span> <span class="token comment" spellcheck="true"> // $event->job
</span> <span class="token comment" spellcheck="true"> // $event->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"><?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->connectionName
</span> <span class="token comment" spellcheck="true"> // $event->job
</span> <span class="token comment" spellcheck="true"> // $event->job->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->connectionName
</span> <span class="token comment" spellcheck="true"> // $event->job
</span> <span class="token comment" spellcheck="true"> // $event->job->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">></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>
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- 請求周期
- 開發環境部署
- Valet
- Homestead
- 核心概念
- 服務提供者
- Facades
- Contracts
- 服務容器
- HTTP 層
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- Session
- 表單驗證
- 前端
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全
- API 認證
- 用戶認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- Redis
- 數據填充
- Eloquent ORM
- Eloquent ORM快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- 序列化
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 錯誤與日志
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度