在 Laravel 6 中,在队列任务中,重试时的指数回退(exponential backoff)的实现
1、在 Laravel 8 中,才原生实现了在队列任务中的指数回退。参考:https://learnku.com/laravel/t/50086
2、参考:http://snags88.github.io/backoff-strategy-for-laravel-jobs 。Backoff Strategy for Laravel Jobs。
3、在现代应用程序中,当出现问题时,会尝试重试导致问题的代码块。例如,如果您在使用 GMail 并且失去了互联网连接,该网站将尝试重新连接到 Google 服务器。您可能会注意到,在第一次尝试时,它会立即尝试重新连接。然后它会等待几秒钟,然后重试。然后等待 10-15 秒,然后重试。然后再等待 30-40 秒,等等。您所经历的称为重新尝试失败任务的退避策略。可以想象,对于排队的作业,采用这种策略会很棒,尤其是对于处理第三方 API 的作业。如果第三方出现故障,那么它可能需要一些时间才能恢复正常,因此采用退避策略是有益的。可悲的是,Laravel 6 的作业中没有内置退避策略。然而,通过它暴露给我们的失败作业处理,我们可以轻松编写自己的可重试作业。我们将实施的特定退避策略称为指数退避策略,因为我们以指数方式增加退避(即 2、4、8、16、32 等)。
4、现有一个 Job,可尝试的最大次数为 3 次。决定在此基础上实现指数回退。现有代码实现
class ThemeAssetUploadJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ThemeAssetGlobPatternTrait; /** * 任务可以尝试的最大次数。 * * @var int */ public $tries = 3; /** * 任务失败的处理过程 * * @param Exception $exception * @return void */ public function failed(Exception $exception) { $themeManager = new ThemeManager(); $themeManager->themeInstallationJobFailed($exception, $this->themeInstallationTask, self::$absolutePath, self::$destination); } }
5、新建 RetriableJob.php
<?php namespace Modules\ThemeStoreDb\Jobs; use Exception; use Illuminate\Foundation\Bus\PendingDispatch; class RetriableJob { public $tries = 1; // 覆盖默认值并确保我们不会自动重新排队 public $currentRetryCount = 1; public $maxRetries = 3; // 3, 9, 27 seconds public $backoffFactor = 3; /** * 任务失败的处理过程 * * @param Exception $exception * @return PendingDispatch|void */ public function failed(Exception $exception) { if ($this->currentRetryCount <= $this->maxRetries) { $this->delay(now()->addSeconds($this->backoffFactor ** $this->currentRetryCount)); $this->currentRetryCount += 1; return dispatch($this); } } }
6、编辑 ThemeAssetUploadJob.php,继承至 RetriableJob,在 handle() 方法添加日志,以记录最终的执行次数
class ThemeAssetUploadJob extends RetriableJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ThemeAssetGlobPatternTrait; /** * 任务可以尝试的最大次数。 * * @var int */ public $tries = 1; /** * Execute the job. * * @return void * @throws OverflowException */ public function handle() { Log::info(date("Y-m-d H:i:s")); aa(); } /** * 任务失败的处理过程 * * @param Exception $exception * @return void */ public function failed(Exception $exception) { parent::failed($exception); $themeManager = new ThemeManager(); $themeManager->themeInstallationJobFailed($exception, $this->themeInstallationTask, self::$absolutePath, self::$destination); } }
7、查看队列任务执行情况。ThemeAssetUploadJob 在第 1 次失败后,总计又重试了 3 次。间隔时间分别为:3、9、27。符合预期。如图1
PS E:\wwwroot\object> php artisan queue:work [2023-04-07 11:26:38][9Eq40nHhxmivDgMMdlFWue9CPLVtgXlB] Processing: Modules\ThemeStoreDb\Jobs\ThemeInstallationJob [2023-04-07 11:27:20][9Eq40nHhxmivDgMMdlFWue9CPLVtgXlB] Processed: Modules\ThemeStoreDb\Jobs\ThemeInstallationJob [2023-04-07 11:27:20][QxjbrKqslQ31DHW4feCXGmFGwg2LNU1t] Processing: Modules\ThemeStoreDb\Jobs\ThemeAssetUploadJob [2023-04-07 11:27:22][QxjbrKqslQ31DHW4feCXGmFGwg2LNU1t] Failed: Modules\ThemeStoreDb\Jobs\ThemeAssetUploadJob [2023-04-07 11:27:25][lbE1431gMXnH5W5gXXmfRbI3fnlumTFt] Processing: Modules\ThemeStoreDb\Jobs\ThemeAssetUploadJob [2023-04-07 11:27:25][lbE1431gMXnH5W5gXXmfRbI3fnlumTFt] Failed: Modules\ThemeStoreDb\Jobs\ThemeAssetUploadJob [2023-04-07 11:27:34][WCq0aET7JOPjiHGxtbJEEt5p4BGrEpko] Processing: Modules\ThemeStoreDb\Jobs\ThemeAssetUploadJob [2023-04-07 11:27:34][WCq0aET7JOPjiHGxtbJEEt5p4BGrEpko] Failed: Modules\ThemeStoreDb\Jobs\ThemeAssetUploadJob [2023-04-07 11:28:01][NhYilL7Uus8oBRNgsxFzrB0x1ZKkl40o] Processing: Modules\ThemeStoreDb\Jobs\ThemeAssetUploadJob [2023-04-07 11:28:01][NhYilL7Uus8oBRNgsxFzrB0x1ZKkl40o] Failed: Modules\ThemeStoreDb\Jobs\ThemeAssetUploadJob
8、再次查看生成的日志记录,以再次确认。总计执行了 4 次。间隔时间分别为:5、9、27。符合预期。如图2
[2023-04-07 11:27:20] local.INFO: 2023-04-07 11:27:20 [2023-04-07 11:27:25] local.INFO: 2023-04-07 11:27:25 [2023-04-07 11:27:34] local.INFO: 2023-04-07 11:27:34 [2023-04-07 11:28:01] local.INFO: 2023-04-07 11:28:01
9、但是,预期是当第 4 次失败后(即真正失败时),才执行 $themeManager->themeInstallationJobFailed() 方法。但是,发现在每次失败后,皆执行了 $themeManager->themeInstallationJobFailed() 方法。编辑 RetriableJob.php 。如图3
<?php namespace Modules\ThemeStoreDb\Jobs; use Exception; use Illuminate\Foundation\Bus\PendingDispatch; class Retriable { public $tries = 1; // 覆盖默认值并确保我们不会自动重新排队 public $currentRetryCount = 1; public $maxRetries = 3; // 3, 9, 27 seconds public $backoffFactor = 3; /** * 任务失败的处理过程 * * @param Exception $exception * @return PendingDispatch|false */ public function failed(Exception $exception) { if ($this->currentRetryCount <= $this->maxRetries) { $this->delay(now()->addSeconds($this->backoffFactor ** $this->currentRetryCount)); $this->currentRetryCount += 1; return dispatch($this); } return false; } }
10、编辑 ThemeAssetUploadJob.php,继承至 RetriableJob。
/** * 任务失败的处理过程 * * @param Exception $exception * @return void */ public function failed(Exception $exception) { if (!parent::failed($exception)) { $themeManager = new ThemeManager(); $themeManager->themeInstallationJobFailed($exception, $this->themeInstallationTask); } }
11、仅当第 4 次失败后(即真正失败时),才执行 $themeManager->themeInstallationJobFailed() 方法。符合预期。如图4
近期评论