PHP code example of kode / limiting

1. Go to this page and download the library: Download kode/limiting library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

kode / limiting example snippets


use Kode\Limiting\Limiter;
use Kode\Limiting\Enum\LimiterType;
use Kode\Limiting\Enum\RedisMode;

// 令牌桶(默认,支持突发)
Limiter::tokenBucket(100, 10.0)->allow('api:user:123');

// 滑动窗口(精确日志)
Limiter::slidingWindow(100, 60.0)->allow('api:user:123');

// 滑动窗口计数器(加权近似,内存恒定)
Limiter::slidingWindowCounter(100, 60)->allow('api:user:123');

// 漏桶(抑制突发)
Limiter::leakyBucket(100, 1.0)->allow('api:user:123');

// 固定窗口计数器
Limiter::counter(1000, 60)->allow('api:user:123');

// 分布式:Redis / Memcached / PDO / APCu($prefix 参数可自定义键前缀,默认 'kode:limiting:')
Limiter::redis(LimiterType::TOKEN_BUCKET, 100, 10.0, '127.0.0.1', 6379, null, 0, RedisMode::STANDALONE, ['127.0.0.1:26379'], 'mymaster', 'myapp:rl:');
Limiter::memcached(LimiterType::TOKEN_BUCKET, 100, 10.0, '127.0.0.1', 11211, 'myapp:rl:');
Limiter::pdo(LimiterType::TOKEN_BUCKET, 100, 10.0, 'mysql:host=127.0.0.1;dbname=limiting', 'root', 'password');
Limiter::apcu(LimiterType::TOKEN_BUCKET, 100, 10.0);

// Redis Sentinel / Cluster 模式(prefix 统一透传到 RedisStore)
Limiter::redis(LimiterType::TOKEN_BUCKET, 100, 10.0, mode: RedisMode::SENTINEL, sentinels: ['192.168.1.1:26379', '192.168.1.2:26379'], masterName: 'mymaster', prefix: 'myapp:rl:');
Limiter::redis(LimiterType::TOKEN_BUCKET, 100, 10.0, mode: RedisMode::CLUSTER, clusterNodes: ['192.168.1.1:7000', '192.168.1.2:7000'], prefix: 'myapp:rl:');

// 多规则组合:10 次/秒 且 100 次/分钟
Limiter::composite([
    'per_second' => Limiter::tokenBucket(10, 10.0)->build(),
    'per_minute' => Limiter::counter(100, 60)->build(),
])->allow('api:user:123');

// 并发控制
Limiter::task(10);      // 任务并发
Limiter::process(10);   // 进程并发
Limiter::fiber(10);     // Fiber 并发
Limiter::semaphore(10); // 计数信号量

// 中间件
$middleware = Limiter::middleware(LimiterType::TOKEN_BUCKET, 100, 10.0);

use Kode\Limiting\Algorithm\TokenBucket;
use Kode\Limiting\Store\MemoryStore;

$store = new MemoryStore();
$bucket = new TokenBucket($store, 100, 10.0);

if ($bucket->allow('user:123', 1)) {
    echo "请求通过";
} else {
    echo "被限流";
}

echo "剩余令牌: " . $bucket->getRemaining('user:123');
echo "等待时间: " . $bucket->getWaitTime('user:123') . "秒";

use Kode\Limiting\Algorithm\CompositeLimiter;
use Kode\Limiting\Algorithm\TokenBucket;
use Kode\Limiting\Algorithm\Counter;
use Kode\Limiting\Store\MemoryStore;

$composite = new CompositeLimiter([
    'per_second' => new TokenBucket(new MemoryStore(), 10, 10.0, 3600, 'c:'),
    'per_minute' => new Counter(new MemoryStore(), 100, 60, 'c:'),
]);

if ($composite->allow('user:1')) {
    // 两条规则同时放行
}

// 定位最先拒绝的规则,便于日志排障
$rule = $composite->firstDeniedRule('user:1');

use Kode\Limiting\Concurrency\TaskLimiter;

// v2.0 签名:create(最大并发, ?存储, 前缀)
$limiter = TaskLimiter::create(5);

if ($limiter->tryAcquire('task:1')) {
    try {
        do_something();
    } finally {
        $limiter->release('task:1');
    }
}

// 自动释放:槽位在回调结束后释放(成功或异常皆然)
$result = $limiter->run('task:2', fn() => do_something());

$limiter->getActiveCount(); // 当前活跃数
$limiter->getAvailable();   // 剩余可用槽位

use Kode\Limiting\Distributed\DistributedLimiter;

// 单机
$limiter = DistributedLimiter::create('127.0.0.1', 6379, 1000, 100.0);
// Sentinel 高可用
$limiter = DistributedLimiter::createSentinel(
    ['192.168.1.1:26379', '192.168.1.2:26379'], 'mymaster', 1000, 100.0
);
// Cluster 分片
$limiter = DistributedLimiter::createCluster(
    ['192.168.1.1:6379', '192.168.1.2:6379', '192.168.1.3:6379'], 1000, 100.0
);

if ($limiter->allow('global:api', 1)) {
    echo "请求通过";
}

// 批量判定:返回 [放行的 key, 被限流的 key]
[$ok, $denied] = $limiter->allowBatch(['k1', 'k2', 'k3']);

use Kode\Limiting\Attribute\RateLimit;

#[RateLimit(capacity: 60, rate: 1.0, key: 'api:{user_id}')]
public function index(): Response { /* ... */ }

// 运行期渲染 key
$key = $attr->resolveKey(['user_id' => 42]); // => 'api:42'

use Kode\Limiting\LimiterManager;

$manager = LimiterManager::fromConfig([
    'stores' => [
        'redis' => ['type' => 'redis', 'host' => '127.0.0.1'],
        // Redis Sentinel / Cluster(storeFromArray 按 mode 自动路由)
        'redis_ha' => ['type' => 'redis', 'mode' => 'sentinel', 'sentinels' => ['192.168.1.1:26379', '192.168.1.2:26379'], 'masterName' => 'mymaster', 'prefix' => 'myapp:rl:'],
        'redis_cluster' => ['type' => 'redis', 'mode' => 'cluster', 'clusterNodes' => ['192.168.1.1:7000', '192.168.1.2:7000'], 'prefix' => 'myapp:rl:'],
    ],
    'limiters' => [
        'api'   => ['type' => 'token_bucket', 'capacity' => 100, 'refillRate' => 10, 'store' => 'redis'],
        'login' => ['type' => 'counter', 'limit' => 5, 'window' => 60], // 未指定 store 则用内存
    ],
]);

$manager->allow('api', 'user:123');                // 是否放行
$result = $manager->consume('login', 'user:456');  // 消耗并取结果
$manager->has('api');                              // 是否已注册
$manager->names();                                 // ['api', 'login']

use Kode\Limiting\Enum\FailurePolicy;
use Kode\Limiting\Store\RedisStore;
use Kode\Limiting\Store\ResilientStore;
use Kode\Limiting\Limiter;

// 默认 fail-open:存储宕机时放行,并把结果标记为降级态
$safe = new ResilientStore(RedisStore::create(), FailurePolicy::ALLOW);
$result = Limiter::tokenBucket(100, 10.0, $safe)->consume('api:user:1');

if ($result->degraded) {
    // 降级放行:记录告警,但请求照常通过
    error_log('限流器降级:' . $result->degradedReason);
}

// 严格模式:存储故障时原样抛出 StoreException
$strict = new ResilientStore(RedisStore::create(), FailurePolicy::RETHROW);

use Kode\Limiting\EventHandler\LimiterEventHandler;
use Kode\Limiting\DTO\LimiterResult;

$handler = new class implements LimiterEventHandler {
    public function onResult(string $key, LimiterResult $result): void {
        if ($result->degraded) { /* 上报降级 */ }
    }
    public function onError(string $key, \Throwable $error): void {
        /* 上报异常 */
    }
};

Limiter::tokenBucket(100, 10.0)->withEventHandler($handler)->consume('api:user:1');

public function get(string $key): ?string;              // 获取值
public function set(string $key, string $value, int $ttl = 0): void; // 设置值,ttl=0 不过期
public function delete(string $key): void;             // 删除键
public function incr(string $key, int $step = 1, int $ttl = 0): int; // 原子递增
public function decr(string $key, int $step = 1): int; // 原子递减
public function ttl(string $key): int;                 // 剩余 TTL(>=0 / -1 永不过期 / -2 不存在)
public function has(string $key): bool;                // 键是否存在且未过期

public function allow(string $key, int $tokens = 1): bool;        // 是否放行
public function check(string $key, int $tokens = 1): LimiterResult; // 只读探测
public function consume(string $key, int $tokens = 1): LimiterResult; // 消耗并返回结果
public function getRemaining(string $key): float;                // 剩余额度
public function getWaitTime(string $key): float;                 // 建议等待秒数
public function reset(string $key): void;                       // 重置
public function getCapacity(): int;                              // 总额度

$result = $bucket->check('key', 1);

$result->allowed;        // 是否放行
$result->remaining;      // 剩余额度
$result->retryAfter;     // 建议重试等待秒数(放行时为 0)
$result->limit;          // 总额度
$result->resetAfter;     // 额度完全恢复所需秒数
$result->timestamp;      // 结果产生时刻(毫秒)
$result->degraded;       // 是否处于降级态(存储故障时兜底放行)
$result->degradedReason; // 触发降级的原因(未降级时为 null)

$result->isAllowed();
$result->isDenied();
$result->toHeaders();     // ['X-RateLimit-Limit'=>..., 'X-RateLimit-Remaining'=>..., 'Retry-After'=>...](被拒时);降级时附 'X-RateLimit-Degraded'=>'1'
$result->toArray();
json_encode($result);    // 等同 toArray()

$config = new LimiterConfig($capacity, $refillRate, $ttl = 3600, $prefix = 'limiter:');
$config->capacity;
$config->refillRate;
$config->ttl;
$config->prefix;

// 链式创建新实例(不可变)
$newConfig = $config->withCapacity(200)->withRefillRate(20.0);

// 从数组创建(兼容 capacity/limit、refillRate/refill_rate/window 等键名)
$config = LimiterConfig::fromArray(['capacity' => 100, 'window' => 60]);

use Kode\Limiting\Store\StoreInterface;

class MyStore implements StoreInterface
{
    public function get(string $key): ?string { /* ... */ }
    public function set(string $key, string $value, int $ttl = 0): void { /* ... */ }
    public function delete(string $key): void { /* ... */ }
    public function incr(string $key, int $step = 1, int $ttl = 0): int { /* ... */ }
    public function decr(string $key, int $step = 1): int { /* ... */ }
    public function ttl(string $key): int { /* ... */ }
    public function has(string $key): bool { /* ... */ }
}