Laravel 11:作业速率限制不超过外部 API 的速率限制

问题描述 投票:0回答:1

由于 Laravel 11 队列现在也可以有速率限制(https://laravel.com/docs/11.x/queues#rate-limiting)。我的 Laravel 应用程序正在向 Shopify API 发出一些请求以获取新产品、向某些订单添加注释以及向订单添加发货信息(例如跟踪号码)。

我有一个 Shopify Basic 计划,允许每秒最多发出 2 个请求,但每分钟不得超过 40 个请求。

现在我想编写一个通用作业,我可以利用它来调用 Shopify API。因此,无论是获取产品还是更新产品,我都希望有一个作业类来处理这一问题,这样我就可以确保我没有超过 Shopify API 速率限制。

但是,我无法完成这项工作。我已经在我的

AppServiceProvider.php
中定义了速率限制,如 Laravel 文档中所述:

public function boot(): void
{
    // Rate limit Shopify API requests set to 2 per second and 40 per minute
    RateLimiter::for('shopify-api-requests', function (object $job) {
        return [
            Limit::perSecond(2),
            Limit::perMinute(40),
        ];
    });
}

这是我的可重用作业类,我想对我向 Shopify API 发出的每个请求重用它:

class ShopifyApiRequestJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $endpoint;
    public $method;
    public $data;
    
    public function __construct(string $endpoint, string $method = 'GET', array $data = null)
    {
        $this->endpoint = $endpoint;
        $this->method = $method;
        $this->data = $data;
    }
    
    public function backoff(): array
    {
        return [1, 5, 10];
    }
    
    public function tries(): int
    {
        return 3;
    }
    
    public function middleware(): array
    {
        return [
            new RateLimited('shopify-api-requests'),
            //new WithoutOverlapping('shopify-api-requests')
        ];
    }
    
    public function handle()
    {
        // Construct the full URL
        $url = 'https://' . config('settings.SHOPIFY_API_DOMAIN') . '/admin/api/' . config('settings.SHOPIFY_API_VERSION') . '/' . $this->endpoint;

        $response = Http::withHeaders([
            'X-Shopify-Access-Token' => config('settings.SHOPIFY_API_KEY'),
            'Content-Type' => 'application/json',
        ])->{$this->method}($url, $this->data);

        // Handle the response as needed (e.g., log it, store it, etc.)
        if ($response->failed()) {
            // Handle failure (e.g., retry the job, log the error, etc.)
            Log::error("Shopify API Request Failed (" . $response->status() . "): " . $response->body() . " " . $url);
        } else {
            // Handle success (e.g., process the response, store it, etc.)
            Log::info("Shopify API Request Successful");
        }
    }
}

当我测试我的工作类别时,它的行为不符合预期。我创建了一个

foreach
循环并已调度我的作业 10 次。

我试图存档的预期结果是,分派的每个作业不与同一类的另一个作业 (ShopifyApiRequestJob) 重叠,并且每秒最多仅处理 2 个作业,每分钟最多处理 30 个作业。

但是,我最终得到了这样的日志:

[2024-08-24 19:29:11] local.INFO: Shopify API Request Successful  
[2024-08-24 19:29:17] local.INFO: Shopify API Request Successful  
[2024-08-24 19:29:20] local.INFO: Shopify API Request Successful  
[2024-08-24 19:29:26] local.ERROR: App\Jobs\ShopifyApiRequestJob has been attempted too many times. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0): [...]
[2024-08-24 19:29:26] local.ERROR: App\Jobs\ShopifyApiRequestJob has been attempted too many times. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0): [...]
[2024-08-24 19:29:26] local.ERROR: App\Jobs\ShopifyApiRequestJob has been attempted too many times. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0): [...]
[2024-08-24 19:29:26] local.ERROR: App\Jobs\ShopifyApiRequestJob has been attempted too many times. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0): [...]
[2024-08-24 19:29:26] local.ERROR: App\Jobs\ShopifyApiRequestJob has been attempted too many times. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0): [...]
[2024-08-24 19:29:26] local.ERROR: App\Jobs\ShopifyApiRequestJob has been attempted too many times. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0): [...]
[2024-08-24 19:29:26] local.ERROR: App\Jobs\ShopifyApiRequestJob has been attempted too many times. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0): [...]

三个作业正在成功处理,但所有其他 7 个作业由于 MaxAttemptsExceededException 而失败。我故意增加了

$backOff
时间来调试它,但没有成功。

我不明白我做错了什么,配置我的工作。我已遵循文档。 有人可以给我建议如何解决这个问题吗?

此外,如果作业的所有重试都失败(而不是每次重试),我希望收到通知。

有人知道如何存档此行为吗?

亲切的问候

php laravel queue jobs laravel-horizon
1个回答
0
投票

要使用 Laravel 的队列系统有效处理 Shopify API 速率限制,您需要解决实施中的几个关键点。

请按照以下步骤操作,希望问题能得到解决:

第一个限速配置:

AppServiceProvider
配置

use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('shopify-api-requests', function () {
        return [
            Limit::perSecond(2)->by('shopify-api'),
            Limit::perMinute(40)->by('shopify-api'),
        ];
    });
}

第二个

Job
配置

工作类别

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Redis\Limit;

class ShopifyApiRequestJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $endpoint;
    public $method;
    public $data;

    public function __construct(string $endpoint, string $method = 'GET', array $data = null)
    {
        $this->endpoint = $endpoint;
        $this->method = $method;
        $this->data = $data;
    }

    public function backoff(): array
    {
        return [2, 10, 20]; // Adjusted backoff times
    }

    public function tries(): int
    {
        return 5; // Increase tries to accommodate retries within the rate limits
    }

    public function middleware(): array
    {
        return [
            new RateLimited('shopify-api-requests'),
        ];
    }

    public function handle()
    {
        // Construct the full URL
        $url = 'https://' . config('settings.SHOPIFY_API_DOMAIN') . '/admin/api/' . config('settings.SHOPIFY_API_VERSION') . '/' . $this->endpoint;

        $response = Http::withHeaders([
            'X-Shopify-Access-Token' => config('settings.SHOPIFY_API_KEY'),
            'Content-Type' => 'application/json',
        ])->{$this->method}($url, $this->data);

        // Handle the response
        if ($response->failed()) {
            // Log failure
            Log::error("Shopify API Request Failed (" . $response->status() . "): " . $response->body() . " " . $url);

            // If all retries fail, log a separate error
            if ($this->attempts() >= $this->tries()) {
                Log::critical("All retries for Shopify API Request Failed: " . $url);
            }

            // Re-throw the exception to allow Laravel to handle retries
            throw new \Exception("Shopify API request failed.");
        } else {
            // Log success
            Log::info("Shopify API Request Successful");
        }
    }
}

第三次处理

Retries
Notifications

作业失败处理

use Illuminate\Support\Facades\Notification;
use App\Notifications\JobFailedNotification;

public function failed(\Exception $exception)
{
    // Notify the admin when all retries have failed
    Notification::route('mail', config('settings.ADMIN_EMAIL'))
        ->notify(new JobFailedNotification($this->endpoint, $exception));
}
© www.soinside.com 2019 - 2024. All rights reserved.