
# 速率限制

Queuety 支持按处理器进行速率限制，以防止外部 API 或其他受限资源过载。处理器达到限制后，工作进程会让其作业保持待处理状态，并在窗口有空余时再次领取。

## 设置速率限制

分发作业时应用速率限制：

```php
use Queuety\Queuety;

Queuety::dispatch( 'call_openai', $payload )
    ->rate_limit( 10, 60 ); // max 10 executions per 60 seconds
```

这两个参数分别是：
1. `$max` - 窗口内允许的最大执行次数
2. `$window` - 窗口时长（秒）

分发时设置的限制只会注册到分发作业的 PHP 进程中。另一个独立进程中的工作进程（例如 `wp queuety work`）看不到这个限制，因此，若要跨进程生效，请按下方所示在处理器的 `config()` 中设置限制（[#3](https://github.com/inline0/queuety/issues/3)）。

## 处理器级速率限制

在处理器的 `config()` 方法中设置速率限制，使其应用于每次分发：

```php
use Queuety\Handler;

class CallOpenAIHandler implements Handler {
    public function handle( array $payload ): void {
        // Call the API...
    }

    public function config(): array {
        return [
            'rate_limit' => [ 60, 60 ], // 60 requests per minute
        ];
    }
}
```

## 工作原理

速率限制按处理器名称跟踪。领取作业后，工作进程会注册处理器的限制（来自其 `config()`），并检查速率限制器。如果处理器已达到限制，工作进程会立即将作业放回 `pending`，不添加延迟，也不消耗一次尝试次数，然后休眠 `QUEUETY_WORKER_SLEEP` 秒再进行下一次轮询。使用 `--once` 启动的工作进程则会退出。

每个工作进程都会在内存中维护一个计数器，并每 5 秒从 `queuety_logs` 表中刷新一次，统计该处理器在过去 `$window` 秒内的 `completed` 日志条目。由于计数来自共享日志表（配置了共享缓存时也会读取共享缓存），处理器级限制会在所有工作进程间生效，而不只是在单个工作进程内生效。失败的执行不会计入限制。

## 编程方式访问

直接访问速率限制器：

```php
$limiter = Queuety::rate_limiter();

// Register a rate limit
$limiter->register( 'call_openai', 60, 60 );

// Check whether a handler has reached its limit
$limited = $limiter->is_limited( 'call_openai' ); // true while the limit is reached

// Seconds until the current window resets
$wait = $limiter->time_until_available( 'call_openai' );
```

`register()` 和 `is_limited()` 操作的是当前 PHP 进程中的限制器。在此注册限制，不会改变独立运行的工作进程所执行的限制。

## 使用场景

- **外部 API 限制。** 将你的速率限制设置为符合 API 的配额（例如 e.g OpenAI 的 60 RPM）。
- **邮件限流。** 防止在短时间内发送过多邮件。
- **资源保护。** 限制占用 CPU 较多的处理器，以免其他作业得不到资源。
