没有不值得去解决的问题,也没有不值得去学习的技术!

在 Laravel 6 中,在队列任务中,重试时的指数回退(exponential backoff)的实现

查看队列任务执行情况。ThemeAssetUploadJob 在第 1 次失败后,总计又重试了 3 次。间隔时间分别为:3、9、27。符合预期

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

查看队列任务执行情况。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

再次查看生成的日志记录,以再次确认。总计执行了 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

但是,预期是当第 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

图4
图4

PHP / Laravel / Yii2 老项目维护与长期技术支持

如果你的 PHP / Laravel / Yii2 项目已经上线,但遇到原开发离职、Bug 长期无人修复、接口不稳定、性能下降、代码难以接手等问题,可以联系我做一次远程技术排查。

适合以下情况:
✅ 老旧 PHP 系统无人维护
✅ Laravel / Yii2 项目 Bug 修复
✅ 后台管理系统小功能迭代
✅ RESTful API 接口排查
✅ MySQL / Redis / Nginx 性能问题
✅ 长期远程兼职维护

可先从一次小问题开始:
✅ 线上报错排查
✅ 接口异常分析
✅ 慢查询与性能瓶颈定位
✅ 代码结构初步评估
✅ 部署环境与日志检查

如需咨询,请联系我,并注明:PHP 维护咨询

联系方式:
Telegram:@shuijingwan
微信:13980074657
邮箱:shuijingwanwq@gmail.com

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理