PHP高并发下API限流与速率控制

讲座主题:PHP高并发下API限流与速率控制

开场白:欢迎来到今天的讲座!

大家好!今天我们要聊一个非常实用的话题——PHP高并发下的API限流与速率控制。想象一下,你的API突然被一大群热情的用户访问,服务器开始冒烟,甚至直接罢工。这可不是什么好事,对吧?所以,我们需要给API加点“刹车”,让它在高峰期也能从容应对。

为了让大家更好地理解这个话题,我会用轻松幽默的语言和实际代码示例来讲解。别担心,不会全是理论,我们会一步步实践起来!


第一讲:为什么需要限流?

假设你开了一家餐厅,只能容纳50人,但突然来了1000人排队。你会怎么办?是让所有人都挤进来,还是有序安排他们进店?答案显然是后者。同样,API也需要这样的“秩序”。

为什么API需要限流?

  1. 保护服务器资源:防止过多请求导致服务器崩溃。
  2. 提升用户体验:避免某些恶意或过量请求影响正常用户。
  3. 公平分配资源:确保每个用户都能获得合理的服务。

国外技术文档中提到,API限流的核心目标是“在不影响核心功能的前提下,最大化系统吞吐量”。听起来很专业,但其实就是“让系统跑得快又不累趴下”。


第二讲:限流的基本策略

限流的方式有很多种,常见的有以下几种:

1. 固定窗口限流

这是最简单的限流方式。比如每分钟允许100个请求。

示例代码:

class FixedWindowLimiter {
    private $maxRequests = 100;
    private $startTime;

    public function __construct() {
        $this->startTime = time();
    }

    public function allowRequest() {
        if (time() - $this->startTime > 60) { // 每分钟重置计数器
            $this->startTime = time();
            return true;
        }
        static $requestCount = 0;
        if ($requestCount < $this->maxRequests) {
            $requestCount++;
            return true;
        }
        return false; // 超过限制
    }
}

$limiter = new FixedWindowLimiter();
if ($limiter->allowRequest()) {
    echo "请求通过";
} else {
    echo "请求被拒绝";
}

缺点:

  • 如果请求集中在窗口的开头或结尾,可能会导致不公平。

2. 滑动窗口限流

滑动窗口限流是对固定窗口的一种改进,它将时间窗口分成多个小片段,从而更精确地控制流量。

示例代码:

class SlidingWindowLimiter {
    private $maxRequests = 100;
    private $requests = [];

    public function allowRequest() {
        $currentTime = time();
        $this->cleanupOldRequests($currentTime);

        if (count($this->requests) < $this->maxRequests) {
            $this->requests[] = $currentTime;
            return true;
        }
        return false;
    }

    private function cleanupOldRequests($currentTime) {
        $threshold = $currentTime - 60; // 过期时间
        foreach ($this->requests as $key => $timestamp) {
            if ($timestamp < $threshold) {
                unset($this->requests[$key]);
            } else {
                break; // 剩余的都在有效期内
            }
        }
    }
}

$limiter = new SlidingWindowLimiter();
if ($limiter->allowRequest()) {
    echo "请求通过";
} else {
    echo "请求被拒绝";
}

优点:

  • 更加精确,避免了固定窗口的突发问题。

3. 令牌桶算法

令牌桶算法是一种动态调整的限流策略。想象一个桶里装着令牌,每次请求都需要消耗一个令牌。如果桶空了,请求就会被拒绝。

示例代码:

class TokenBucketLimiter {
    private $capacity = 100; // 桶容量
    private $rate = 1;       // 每秒生成1个令牌
    private $tokens = 0;
    private $lastTimestamp;

    public function __construct() {
        $this->lastTimestamp = time();
        $this->tokens = $this->capacity;
    }

    public function allowRequest() {
        $this->addTokens();
        if ($this->tokens >= 1) {
            $this->tokens--;
            return true;
        }
        return false;
    }

    private function addTokens() {
        $now = time();
        $elapsedTime = $now - $this->lastTimestamp;
        $newTokens = $elapsedTime * $this->rate;
        $this->tokens = min($this->capacity, $this->tokens + $newTokens);
        $this->lastTimestamp = $now;
    }
}

$limiter = new TokenBucketLimiter();
if ($limiter->allowRequest()) {
    echo "请求通过";
} else {
    echo "请求被拒绝";
}

优点:

  • 动态调整,适合处理突发流量。

4. 漏桶算法

漏桶算法类似于令牌桶,但它以固定速率释放请求。即使桶满了,多余的请求也会被丢弃。

示例代码:

class LeakyBucketLimiter {
    private $capacity = 100; // 桶容量
    private $rate = 1;       // 每秒流出1个请求
    private $water = 0;
    private $lastTimestamp;

    public function __construct() {
        $this->lastTimestamp = time();
    }

    public function allowRequest() {
        $this->leakWater();
        if ($this->water < $this->capacity) {
            $this->water++;
            return true;
        }
        return false;
    }

    private function leakWater() {
        $now = time();
        $elapsedTime = $now - $this->lastTimestamp;
        $leakedWater = $elapsedTime * $this->rate;
        $this->water = max(0, $this->water - $leakedWater);
        $this->lastTimestamp = $now;
    }
}

$limiter = new LeakyBucketLimiter();
if ($limiter->allowRequest()) {
    echo "请求通过";
} else {
    echo "请求被拒绝";
}

优点:

  • 稳定性高,适合对延迟要求严格的场景。

第三讲:选择合适的限流策略

不同的限流策略适用于不同的场景。以下是它们的对比表:

策略 实现复杂度 突发流量支持 精确度
固定窗口 ★★
滑动窗口 ★★★ ★★ ★★★
令牌桶 ★★★★ ★★★ ★★★★
漏桶 ★★★★ ★★★★

第四讲:实战演练

让我们用Redis实现一个分布式的令牌桶限流器。Redis的高性能和分布式特性非常适合处理高并发场景。

示例代码:

class RedisTokenBucketLimiter {
    private $redis;
    private $key;
    private $capacity;
    private $rate;

    public function __construct($redis, $key, $capacity, $rate) {
        $this->redis = $redis;
        $this->key = $key;
        $this->capacity = $capacity;
        $this->rate = $rate;
    }

    public function allowRequest() {
        $now = time();
        $lastTimestamp = $this->redis->get($this->key . ':timestamp') ?: $now;
        $tokens = $this->redis->get($this->key . ':tokens') ?: $this->capacity;

        $elapsedTime = $now - $lastTimestamp;
        $newTokens = min($this->capacity, $tokens + $elapsedTime * $this->rate);

        if ($newTokens >= 1) {
            $this->redis->set($this->key . ':tokens', $newTokens - 1);
            $this->redis->set($this->key . ':timestamp', $now);
            return true;
        }

        $this->redis->set($this->key . ':tokens', $newTokens);
        $this->redis->set($this->key . ':timestamp', $now);
        return false;
    }
}

// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$limiter = new RedisTokenBucketLimiter($redis, 'api:limiter', 100, 1);
if ($limiter->allowRequest()) {
    echo "请求通过";
} else {
    echo "请求被拒绝";
}

结语:总结与展望

今天我们一起探讨了PHP高并发下的API限流与速率控制。从简单的固定窗口到复杂的令牌桶和漏桶算法,我们学习了多种限流策略及其适用场景。最后,我们还用Redis实现了一个分布式的限流器。

记住,限流不是为了阻止用户,而是为了让系统更加稳定和高效。希望今天的讲座能给大家带来一些启发!

如果你有任何问题或想法,欢迎在评论区留言。下次见!

发表回复

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