# laravel Queue
## Horizon
管控job的 要有redis
fail的可以retry
然後可以加上tag 讓你知道你傳入的參數
可以建立多個worker監聽
https://www.youtube.com/watch?v=Z5w0z6Ar3tQ&ab_channel=AndreMadarang
https://laracasts.com/series/learn-laravel-horizon/episodes/1
## 執行前被刪除
https://www.youtube.com/watch?v=58SELaqUKuI&ab_channel=LaravelDaily
## 不要太多參數 會佔raw
https://www.youtube.com/watch?v=SDHNacIrqhI&ab_channel=LaravelDaily
要讓 queue:work 進程永久在後台運行,你應該使用進程監控工具,比如 Supervisor 來保證隊列處理器沒有停止運行
https://topic.alibabacloud.com/tc/a/introduction-to-the-laravel-queue-system_4_86_30933947.html
索相關
了解trait
https://darkghosthunter.medium.com/laravel-what-does-every-trait-in-a-job-class-6039707b851b
進階
https://laracasts.com/series/laravel-queue-mastery/episodes/3
必看
https://medium.com/@d101201007/laravel-queue%E9%9A%8A%E5%88%97%E5%85%A5%E9%96%80%E4%BB%8B%E7%B4%B9-%E6%8C%87%E4%BB%A4%E6%95%99%E5%AD%B8-use-laravel-queue-ad3699c76b81
實現 Laravel 隊列的 3 個簡單步驟
https://www.zealousweb.com/3-easy-steps-to-implement-laravel-queue/
哥布林
https://pandalab.org/articles/107
非常重要 必看 進階版
https://dev.to/mvpopuk/laravel-queues-pocket-guide-c6a
當有人點菜(Job::Dispatch()),就會產生一筆 訂單(queue)
送進 廚房(Redis); 廚師(Queue Worker) 看到廚房(Redis) 裡面有
一筆訂單(queue) 就會依訂單(queue)上的 食譜(Job) 執行。
進階版
https://laracasts.com/series/laravel-queue-mastery/episodes/1
有點多
https://freek.dev/1734-how-to-group-queued-jobs-using-laravel-8s-new-batch-class
Q&A
https://medium.com/learn-or-die/laravel-digging-deeper-queues-%E5%AE%98%E6%96%B9%E6%96%87%E4%BB%B6%E5%8E%9F%E5%AD%90%E5%8C%96%E7%BF%BB%E8%AD%AF%E7%AD%86%E8%A8%98-ed68eb01eac2
## 基本觀念
在開始使用 Laravel 隊列前,弄明白 「串連」 和 「隊列」 的區別是很重要的。在你的 config/queue.php 設定檔裡, 有一個 connections 配置選項。 這個選項給 Amazon SQS, Beanstalk ,或者 Redis 這樣的後端服務定義了一個特有的串連。不管是哪一種,一個給定的串連可能會有多個「隊列」,而 「隊列」可以被認為是不同的棧或者大量的隊列任務。
Laravel 隊列處理器允許你定義隊列的優先順序,所以你能給不同的隊列劃分不同的優先順序或者區分不同任務的不同處理方式了。比如說,如果你把任務推到 high 隊列中,你就能讓隊列處理器優先處理這些任務了
像圖片內容這種位元據, 在放入隊列任務之前必須使用 base64_encode 方法轉換一下。 否則,當這項任務放置到隊列中時,可能無法正確序列化為 JSON。
model要序列化
## 基本
消息通
过Job类的实例封装后再通过序列化封装成json格式,然后将其发送出
去
不同的消息类型可以封装不同
的数据和数据处理方法。消息队列的建立是通过实例化相应的消息队列
类来完成的,如同步类型的消息队列其实就是
```
Illuminate\Queue
\SyncQueue
```
类的实例,而数据库类型的消息队列就是
```
Illuminate\Queue
\DatabaseQueue
```
类的实例,不同消息队列类的实例管理不同的消息存储
方式,如同步类型的消息队列直接将消息(也可称为工作)生成同步工
作实例,即Illuminate\Queue\Jobs\ Job类实例,而数据库类型的消息队列
则是将消息序列化后存储在数据库中。不同的消息队列维护着不同的消
息存储方式。消息发送就是将封装并序列化后的消息存储在消息队列维
护的存储空间中,这种存储空间可能是多种形式的,如文件、内存和数
据库等
在Laravel中,消息队列的生成和发送实现
起来非常简单,只要在需要消息发送的类中包含
`Illuminate\Foundation\Bus\DispatchesJobs`模块即可,这个模块其实是一
个trait,相当于Ruby中的模块(module),主要用于单继承的面向对象
语言实现代码的共享,相应的介绍可以参考本书PHP的重要性质的相关
内容。通过包含该模块,并直接调用其中的dispatch()方法就可以实现消
息队列的生成和发送。在Laravel框架默认的控制器中(文件
app/Http/Controllers/Controller.php)就包含该模块,
文件
```
laravel\app\Http\Controllers\WelcomeController.php
public function index()
{
$this->dispatch(new QueuedTest());
return view('welcome');
}
```
## 處理已刪除模型上的隊列
這部分是當您的工作失敗時。例如,假設您的用戶決定刪除他的帳戶。模型從數據庫中刪除,但在出去之前,我們會將電子郵件放入“黑名單”30 天,以便有人無法使用相同的電子郵件,並將告別電子郵件排隊。我們通過 User 模型,並完成。
```
/**
* 刪除用戶賬號
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function deleteAccount(Request $request)
{
$request->user()->delete ();
dispatch(new GoodbyeUser($request->user()));
return response()->view('user.goodbye');
}
```
第二天早上醒來,作業失敗了:當作業被反序列化時,它找不到模型,所以它返回一個ModelNotFoundException. 該死!
這就是為什麼,當您傳遞一個注定要被刪除(或已被刪除)的模型時,將需要的屬性傳遞給作業工作要好得多,在這種情況下,它只是電子郵件和用戶名。
```
/**
* 刪除用戶賬號
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function deleteAccount(Request $request)
{
$user = $request->user() ;
$user->delete();
dispatch(new GoodbyeUser($user->email, $user->name);
}
```
## dispatch
加入用dispatch
如果是通知noti
用send跟(多)
notify
## dispatch and event
兩種一樣
https://learnku.com/laravel/t/67039
## 一些觀念整合
多個工作 chain 有一個會失效
所以8之後有出batch 其中的方法看上面進階版
unique interface獨特工作是 對應lock的唯一(如果您只需要限製作業的並發處理,請改用WithoutOverlapping作業中間件)
這是應對多進程的鎖
然後batch應對多現成的問題
**小心資料庫資料跟現在的不一樣
是當時存進去的
所以最好要判斷資料在不在**
## 容易犯的錯誤
第一失敗的table
要自己建立用

第二 如果你有更動code 你開兩個
一個retry也不會過
原因是他的code還是在舊的
所以要重新啟動
原因可能是cache的問題
下面是restart的解說
此命令將指示所有隊列工作人員在處理完當前作業後優雅地“死亡”,以便不會丟失現有作業。
隊列使用緩存來存儲重啟信號,因此在使用此功能之前,您應該驗證為您的應用程序正確配置了緩存驅動程序。
由於在執行 queue:restart 命令時隊列工作器會死亡,因此您應該運行一個進程管理器(例如 Supervisor)來自動重新啟動隊列工作器。
這意味著 queue:restart 不會立即重新啟動隊列,而是簡單地“廣播隊列重新啟動信號”,以便隊列工作人員在完成當前分配的任務後使其死亡。
## ShouldQueue 介面直接實現
https://stackoverflow.com/questions/48370140/laravel-shouldqueue-how-does-it-work
Internally Laravel checks if Job or Mailable or Notification etc implements ShouldQueue interface. For example:
`if ($job instanceof ShouldQueue) {}`
看上面解答
所以只要Job 跟 Mail 跟 Notification vs event
都只要介面實現就好
總結別想太多
你看job就是直接實現介面
所以你要在哪邊用job都可以
但如果要onQueque之類的使用方法要用trait
Queueable
## 基本介紹
任務類別的結構很簡單,一般來說只會包含一個讓隊列用來呼叫此任務的 handle 方法
## job內使用 model
https://dev.to/ryancco/understanding-laravel-s-serializesmodels-trait-a6e
在任務類別的建構子中直接傳遞了一個 Eloquent 模型。因為我們在任務類別裡引用了 SerializesModels 這個 trait,使得 Eloquent 模型在處理任務的時候可以被優雅地序列化和反序列化
**問題**
為什麼要用trait不是能直接注入嗎
當複雜的對像被序列化時,它們的字符串表示可能會非常長,佔用隊列和應用程序服務器上不必要的資源。
**解決方案**
正因為如此,Laravel 提供了一個叫做 `trait` 的特性`SerializesModels`,當它被添加到一個對像中時,它會找到任何類型的屬性Model或Eloquent\Collection在序列化過程中,並將它們替換為一個簡單的舊 PHP 對象(POPO),稱為`ModelIdentifier`. 這些標識符對象表示原始屬性Model類型和 ID,或在 的情況下的 ID,`Eloquent\Collection`序列化時字符串表示形式要小得多。當這些對像被反序列化時,`ModelIdentifiers` 將被替換為它們臨時表示的s的Modelor 。`Eloquent\CollectionModel`
ℹ️ 想知道`SerializesModelstrait`如何在運行時“替換”這些屬性?在進入源代碼之前,您可能需要閱讀PHP 文檔以快速了解反射 API 提供的內容


## 重新放回去job裡面

後面數字是uuid
## 限速(需要redis)

你看 mailTrap 一次發送大量可能會出錯
可能別家的也會擋 當作參考
像這種延遲 不要天真的延遲eamil
要延遲jobs
可能多個地方會用到
這建立一個job 然後在用這個去呼叫email之類的
如果要限制email
建構子那邊用mailable 所有mail都繼承他
## queue條件啟動
```
<?php
namespace App\Listeners;
use App\Events\OrderPlaced;
use Illuminate\Contracts\Queue\ShouldQueue;
class RewardGiftCard implements ShouldQueue
{
public function handle(OrderPlaced $event)
{
//
}
public function shouldQueue(OrderPlaced $event)
{
return $event->order->subtotal >= 5000;
}
}
```
Answer: 當 Order model 的 subtotal attribute 大於等於 5000 時, queue 該 listener
## Handling Failed Jobs
```
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
public function handle(OrderShipped $event)
{
//
}
public function failed(OrderShipped $event, $exception)
{
//
}
}
```
Answer: 在 listener class 內定義 failed(), 當 listener job failed 時, 執行 failed() 內的邏輯
## SerializesModels
```
<?php
namespace App\Jobs;
use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The podcast instance.
*
* @var \App\Models\Podcast
*/
protected $podcast;
/**
* Create a new job instance.
*
* @param App\Models\Podcast $podcast
* @return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
/**
* Execute the job.
*
* @param App\Services\AudioProcessor $processor
* @return void
*/
public function handle(AudioProcessor $processor)
{
// Process uploaded podcast...
}
}
```
在這個例子中,請注意我們能夠將Eloquent 模型直接傳遞到排隊作業的構造函數中。由於SerializesModels作業使用的特性,當作業正在處理時,Eloquent 模型及其加載的關係將被優雅地序列化和反序列化。
如果您的排隊作業在其構造函數中接受 Eloquent 模型,則只有模型的標識符將被序列化到隊列中。當實際處理作業時,隊列系統會自動從數據庫中重新檢索完整的模型實例及其加載的關係。這種模型序列化方法允許將更小的作業負載發送到您的隊列驅動程序。
## Unique Jobs
**注意:唯一的任务需要支持 locks 的缓存驱动程序。目前,memcached, redis, dynamodb, database, file, 和 array 缓存驱动程序支持原子锁。此外,唯一任务约束不适用于批量的任务。**
需要一個支持鎖的緩存驅動程序
有時,您可能希望確保在任何時間點隊列中只有一個特定作業的實例。你可以通過**ShouldBeUnique**在你的工作類上實現接口來做到這一點。這個接口不需要你在你的類上定義任何額外的方法:
```
<?php
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
...
}
```
在某些情況下,您可能想要定義一個特定的“鍵”,使作業唯一,或者您可能想要指定一個超時,超過該時間作業不再保持唯一。要做到這一點,你可以定義uniqueId和uniqueFor你的工作類的屬性或方法:
```
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
/**
* The product instance.
*
* @var \App\Product
*/
public $product;
/**
* The number of seconds after which the job's unique lock will be released.
*
* @var int
*/
public $uniqueFor = 3600;
/**
* The unique ID of the job.
*
* @return string
*/
public function uniqueId()
{
return $this->product->id;
}
}
```
在上面的示例中,UpdateSearchIndex作業通過產品 ID 是唯一的。因此,在現有作業完成處理之前,將忽略具有相同產品 ID 的作業的任何新調度。此外,如果現有作業在一小時內沒有被處理,唯一鎖將被釋放,另一個具有相同唯一鍵的作業可以被分派到隊列中。
時間的話fumction uniqueFor

## redis middware
作業中間件允許您圍繞排隊作業的執行包裝自定義邏輯,從而減少作業本身的樣板。例如,考慮以下handle方法,它利用 Laravel 的 Redis 速率限制功能,每五秒只允許處理一個作業:
```
use Illuminate\Support\Facades\Redis;
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () {
info('Lock obtained...');
// Handle job...
}, function () {
// Could not obtain lock...
return $this->release(5);
});
}
```
## 防止工作重疊
這是schedule會用到的但這邊也能用
原因是我猜queue他本身就是用schefule去處發所以他也可以用這個
因為這個middware會去檢查她是不是使用schefule
**如果您只需要限製作業的並發處理,請改用WithoutOverlapping作業中間件。**
不然就用lock
Laravel 包含一個Illuminate\Queue\Middleware\WithoutOverlapping中間件,可讓您防止基於任意鍵的作業重疊。當排隊作業正在修改一次只能由一個作業修改的資源時,這會很有幫助。
例如,假設您有一個更新用戶信用評分的排隊作業,並且您希望防止同一用戶 ID 的信用評分更新作業重疊。為此,您可以WithoutOverlapping從作業的middleware方法中返回中間件:
```
use Illuminate\Queue\Middleware\WithoutOverlapping;
/**
* Get the middleware the job should pass through.
*
* @return array
*/
public function middleware()
{
return [new WithoutOverlapping($this->user->id)];
}
```
一樣schedule也用到這個
### 在處理開始之前保持作業的唯一性
默認情況下,在作業完成處理或所有重試嘗試失敗後,將“解鎖”唯一作業。但是,在某些情況下,您可能希望作業在處理之前立即解鎖。為此,您的工作應該執行ShouldBeUniqueUntilProcessing合同而不是ShouldBeUnique合同:
```
class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
// ...
}
```
## timeout 允許作業運行的最大秒數
```
<?php
namespace App\Jobs;
class ProcessPodcast implements ShouldQueue
{
/**
* The number of seconds the job can run before timing out.
*
* @var int
*/
public $timeout = 120;
}
```
## 最大嘗試次數
```
<?php
namespace App\Jobs;
class ProcessPodcast implements ShouldQueue
{
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 5;
}
```
## 工人睡眠時間 sleep
可寫在hanle裡面
跟queeue:work --sleep=2
## 重試失敗的作業
retry
這邊要用最好看文黨
## 作業鏈 多個job
作業鏈允許您指定在主作業成功執行後應按順序運行的排隊作業列表。如果序列中的一項作業失敗,則其餘作業將不會運行。要執行排隊的作業鏈,您可以使用Facadechain提供的方法Bus。Laravel 的命令總線是一個較低級別的組件,隊列作業調度建立在:
```
use App\Jobs\OptimizePodcast;
use App\Jobs\ProcessPodcast;
use App\Jobs\ReleasePodcast;
use Illuminate\Support\Facades\Bus;
Bus::chain([
new ProcessPodcast,
new OptimizePodcast,
new ReleasePodcast,
])->dispatch();
```
還可以tye catch之類的很多用法
### 定義可批處理作業
https://dev.to/mvpopuk/laravel-queues-pocket-guide-c6a
跟chaim差別 上面的array裡面有一個失敗就停止
這不一樣 所以用這個(當然要加上allowFailures 不然就跟上面一樣)
Illuminate\Bus\Batchable特徵添加到作業類。此特徵提供對batch方法的訪問,該方法可用於檢索作業正在執行的當前批處理:
看文黨
實際是會多一張表
存作業

### batch的進度條
https://www.youtube.com/watch?v=HErI5i-a0NI
## 限制異常
Laravel 包含一個`Illuminate\Queue\Middleware\ThrottlesExceptions`允許您限制異常的中間件。一旦作業拋出給定數量的異常,所有進一步執行作業的嘗試都會延遲,直到指定的時間間隔過去。此中間件對於與不穩定的第三方服務交互的作業特別有用。
例如,讓我們想像一個排隊的作業與開始拋出異常的第三方 API 交互。要限制異常,您可以ThrottlesExceptions從作業的middleware方法中返回中間件。通常,此中間件應與實現基於時間的嘗試的作業配對:
## 內存問題
不過用chuckById感覺就夠了
https://freek.dev/1734-how-to-group-queued-jobs-using-laravel-8s-new-batch-class
## 隊列優先順序
有時候你希望設定處理隊列的優先順序。比如在 config/queue.php 裡你可能設定了 redis 串連中的預設隊列優先順序為 low,但是你可能偶爾希望把一個任務推到 high 優先順序的隊列中,像這樣:
`dispatch((new Job)->onQueue('high'));`
要驗證 high 隊列中的任務都是在 low 隊列中的任務之前處理的,你要啟動一個隊列處理器,傳遞給它隊列名字的列表並以英文逗號,間隔:
`php artisan queue:work --queue=high,low`
## 隊列處理器 & 部署
因為隊列處理器都是 long-lived 進程,如果代碼改變而隊列處理器沒有重啟,他們是不能應用新代碼的。所以最簡單的方式就是重新部署過程中要重啟隊列處理器。你可以很優雅地只輸入 `queue:restart` 來重啟所有隊列處理器。
`php artisan queue:restart`
這個命令將會告訴所有隊列處理器在執行完當前任務後結束進程,這樣才不會有任務丟失。因為隊列處理器在執行 `queue:restart` 命令時對結束進程,你應該運行一個進程管理器,比如 Supervisor 來自動重新啟動隊列處理器。
## 隊列處理器逾時
`queue:work Artisan` 命令對外有一個 --timeout 選項。這個選項指定了 Laravel 隊列處理器最多執行多長時間後就應該被關閉掉。有時候一個隊列的子進程會因為很多原因僵死,比如一個外部的 HTTP 要求沒有響應。這個 --timeout 選項會移除超出指定事件節流的僵死進程。
`php artisan queue:work --timeout=60`
retry_after 配置選項和 --timeout 命令列選項是不一樣的,但是可以同時工作來保證任務不會丟失並且不會重複執行。
--timeout 應該永遠都要比 retry_after 短至少幾秒鐘的時間。這樣就能保證任務進程總能在失敗重試前就被殺死了。如果你的 --timeout 選項大於 retry_after 配置選項,你的任務可能被執行兩次。
###### tags: `Laravel`