有時候一個定時任務執行需要的時間可能會比我們想像的要長,這就會引起一個問題—當前任務還沒執行完畢的時候另一個相同的任務也會執行,導致任務重複。 例如想像一下我們執行每分鐘產生一次報告的任務,在經過一段時間後,資料量變得很大導致執行時間多於1分鐘,這樣就會導致在上一個任務還沒結束的時候另一個相同的任務開始執行。
大部分情況下是沒有什麼問題的,但是有時我們需要避免這種情況來保證獲得正確的資料。在Laravel中我們可以透過withoutOverlapping
方法來處理:
$schedule->command('mail:send')->withoutOverlapping();
Laravel會檢查Console\Scheduling\Event::withoutOverlapping
屬性,如果該值為true那麼將會針對這個任務建立一個互斥鎖(mutex),並且只有在可以建立互斥鎖的情況下才會執行此任務。
這是我在網路上找到的最有趣的解釋:
當我們在開會進行激烈的討論時,我會從我桌子裡拿出來一個尖叫雞。只有手裡拿著尖叫雞的人才能說話,如果你沒有拿著尖叫雞你是不能說話的。你只能向會議主持人請示,只有在你拿到尖叫雞的時候你才能說話否則只能等待。當你講話完畢的時候,將尖叫雞還給會議主持人,主持人會將尖叫雞給到下一個讓其說話。這樣會確保人們不會互相交談,同時他們也會有自己的時間來進行演講。
將尖叫雞換成互斥鎖,人換成線程。你基本上就有了一個互斥鎖的基本概念。
-- https://stackoverflow.com/questions/34524/...
Laravel在第一次執行任務的時候會建立一個互斥鎖,然後在每次執行任務時會檢查互斥鎖是否存在,只有互斥鎖不存在的時候任務才會執行。以下是withoutOverlapping
方法:
public function withoutOverlapping() { $this->withoutOverlapping = true; return $this->then(function () { $this->mutex->forget($this); })->skip(function () { return $this->mutex->exists($this); }); }
Laravel建立了一個過濾回呼方法來告訴計畫管理器忽略互斥鎖仍然存在的任務,同時也建立了一個在完成任務實例後清除互斥鎖的回調。同時,在執行任務之前,Lravel會在Console\Scheduling\Event::run()
方法中依序執行下面一系列的檢查:
if ($this->withoutOverlapping && ! $this->mutex->create($this)) { return; }
那麼互斥鎖的屬性是從哪裡來的呢?
當Console\Scheduling\Schedule
被實例化的時候,Laravel會檢查Console\Scheduling\Mutex
是否綁定到了容器,如果是那麼就會實例化它,否則會使用Console\Scheduling\CacheMutex
$this->mutex = $container->bound(Mutex::class) ? $container->make(Mutex::class) : $container->make(CacheMutex::class);
現在當任務管理器在註冊事件的時候會將互斥鎖的實例一併傳進去:
$this->events[] = new Event($this->mutex, $command);
Laravel預設使用了快取實現的互斥鎖,但你可以自己實作並替換它。
CacheMutex類別只有3個簡單的方法,它使用了事件互斥鎖的名字作為快取的鍵值:
public function create(Event $event) { return $this->cache->add($event->mutexName(), true, 1440); } public function exists(Event $event) { return $this->cache->has($event->mutexName()); } public function forget(Event $event) { $this->cache->forget($event->mutexName()); }
就就像我們之前看過的,管理器註冊了一個執行後回調來保證任務執行完畢的時候移除互斥鎖,對於一個系統裡的命令來說也許已經可以確保移除了。但是對於一個回呼方法的任務來說腳本可能在執行回調的時候結束,因此為了避免這種情況在Console\Scheduling\CallbackEvent::run()
方法中加入了下面的程式碼確保互斥鎖在任務意外關閉的時候能夠正常移除:
register_shutdown_function(function () { $this->removeMutex(); });
更多Laravel相關技術文章,請訪問Laravel教程專欄進行學習!
以上是Laravel是如何防止你的計時任務重複執行的的詳細內容。更多資訊請關注PHP中文網其他相關文章!