最新公告
  • 欢迎您光临源码库,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入
  • PHP后端限流算法比较

    PHP后端限流算法比较插图

    PHP后端限流算法比较:从简单计数到漏桶与令牌桶实战

    上周在优化API网关时,我又遇到了那个熟悉的问题:某个热门接口突然被刷,导致整个服务响应变慢。经过一番折腾,我重新梳理了PHP中几种常见的限流方案,今天就把这些实战经验分享给大家,特别是三种核心算法的实现对比。

    1. 为什么我们需要限流?

    记得第一次做电商促销活动时,由于没有做限流,瞬间涌入的请求直接把数据库打挂了。从那以后我明白了:限流不只是防止恶意请求,更是保障系统稳定性的重要手段。它能:

    • 避免单个接口耗尽服务器资源
    • 防止爬虫过度抓取
    • 保证核心业务的稳定性
    • 实现服务的优雅降级

    2. 固定窗口计数器:最简单的开始

    这是我最早接触的限流方式,思路很简单:在固定时间窗口内计数,超过阈值就拒绝。

    
    class FixedWindowLimiter
    {
        private $redis;
        private $key;
        private $maxRequests;
        private $windowSize;
        
        public function __construct($redis, $key, $maxRequests = 100, $windowSize = 60)
        {
            $this->redis = $redis;
            $this->key = $key;
            $this->maxRequests = $maxRequests;
            $this->windowSize = $windowSize;
        }
        
        public function isAllowed()
        {
            $current = $this->redis->get($this->key);
            
            if ($current === false || $current < $this->maxRequests) {
                $this->redis->multi();
                $this->redis->incr($this->key);
                $this->redis->expire($this->key, $this->windowSize);
                $this->redis->exec();
                return true;
            }
            
            return false;
        }
    }
    

    踩坑提醒:这种算法在窗口切换时会有临界问题。比如限制每分钟100次,可能在59秒和61秒分别请求100次,实际上2秒内请求了200次。

    3. 滑动窗口:解决临界问题

    为了解决固定窗口的缺陷,我采用了滑动窗口算法,记录更细粒度的时间片:

    
    class SlidingWindowLimiter
    {
        private $redis;
        private $key;
        private $maxRequests;
        private $windowSize;
        private $precision;
        
        public function __construct($redis, $key, $maxRequests = 100, $windowSize = 60, $precision = 10)
        {
            $this->redis = $redis;
            $this->key = $key;
            $this->maxRequests = $maxRequests;
            $this->windowSize = $windowSize;
            $this->precision = $precision; // 时间片精度(秒)
        }
        
        public function isAllowed()
        {
            $now = time();
            $windowStart = $now - $this->windowSize;
            
            // 移除过期时间片
            $this->redis->zRemRangeByScore($this->key, 0, $windowStart);
            
            // 获取当前窗口内请求数
            $current = $this->redis->zCard($this->key);
            
            if ($current < $this->maxRequests) {
                $this->redis->zAdd($this->key, $now, uniqid());
                $this->redis->expire($this->key, $this->windowSize * 2);
                return true;
            }
            
            return false;
        }
    }
    

    4. 漏桶算法:平滑流量输出

    在处理消息队列消费时,我发现漏桶算法特别适合控制处理速率:

    
    class LeakyBucketLimiter
    {
        private $redis;
        private $key;
        private $capacity;    // 桶容量
        private $leakRate;    // 漏水速率(个/秒)
        
        public function __construct($redis, $key, $capacity = 100, $leakRate = 10)
        {
            $this->redis = $redis;
            $this->key = $key;
            $this->capacity = $capacity;
            $this->leakRate = $leakRate;
        }
        
        public function isAllowed()
        {
            $now = microtime(true);
            $data = $this->redis->hGetAll($this->key);
            
            if (empty($data)) {
                $this->redis->hMSet($this->key, [
                    'water' => 1,
                    'last_time' => $now
                ]);
                $this->redis->expire($this->key, 3600);
                return true;
            }
            
            $water = (float)$data['water'];
            $lastTime = (float)$data['last_time'];
            
            // 计算漏水量
            $leakAmount = ($now - $lastTime) * $this->leakRate;
            $water = max(0, $water - $leakAmount);
            
            if ($water + 1 <= $this->capacity) {
                $this->redis->hMSet($this->key, [
                    'water' => $water + 1,
                    'last_time' => $now
                ]);
                return true;
            }
            
            return false;
        }
    }
    

    5. 令牌桶算法:应对突发流量

    在需要应对突发流量的场景下,令牌桶是我的首选:

    
    class TokenBucketLimiter
    {
        private $redis;
        private $key;
        private $capacity;    // 桶容量
        private $fillRate;    // 令牌添加速率(个/秒)
        
        public function __construct($redis, $key, $capacity = 100, $fillRate = 10)
        {
            $this->redis = $redis;
            $this->key = $key;
            $this->capacity = $capacity;
            $this->fillRate = $fillRate;
        }
        
        public function isAllowed($tokens = 1)
        {
            $now = microtime(true);
            $data = $this->redis->hGetAll($this->key);
            
            if (empty($data)) {
                $this->redis->hMSet($this->key, [
                    'tokens' => $this->capacity - $tokens,
                    'last_time' => $now
                ]);
                $this->redis->expire($this->key, 3600);
                return true;
            }
            
            $tokens = (float)$data['tokens'];
            $lastTime = (float)$data['last_time'];
            
            // 计算新增令牌
            $newTokens = ($now - $lastTime) * $this->fillRate;
            $tokens = min($this->capacity, $tokens + $newTokens);
            
            if ($tokens >= 1) {
                $this->redis->hMSet($this->key, [
                    'tokens' => $tokens - 1,
                    'last_time' => $now
                ]);
                return true;
            }
            
            return false;
        }
    }
    

    6. 实战场景选择建议

    经过多个项目的实践,我总结出这样的选择指南:

    • API限流:滑动窗口或令牌桶,平衡精度和性能
    • 消息队列消费:漏桶算法,保证匀速处理
    • 突发流量应对:令牌桶算法,充分利用系统资源
    • 简单场景:固定窗口计数器,快速实现

    记得在分布式环境下,一定要用Redis等外部存储来保证计数的一致性。本地内存计数只适合单机部署。

    7. 我的踩坑总结

    最后分享几个实际踩过的坑:

    • Redis连接超时要做好重试机制,否则限流本身会成为单点故障
    • 时间同步问题:确保所有服务器时间一致
    • 内存泄漏:记得设置合适的key过期时间
    • 性能测试:在实际流量下验证算法效果

    限流不是银弹,需要根据具体业务场景灵活选择。希望这些经验能帮你少走弯路!

    1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
    2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
    3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
    4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
    5. 如有链接无法下载、失效或广告,请联系管理员处理!
    6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!

    源码库 » PHP后端限流算法比较