您现在的位置是:网站首页 > 心得笔记

PHP对API进行限流

盛悦2025-02-24162人围观
简介在开发API时,为了防止恶意请求和保证系统的稳定性,我们经常需要对API进行限流。本文将介绍如何使用PHP对API进行限流。

为什么要限流

限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自身系统和下游系统不被巨型流量冲垮等


限流的思想

在保证可用的情况下尽可能多增加进入的人数,其余的人在排队等待,或者返回友好提示,保证里面的进行系统的用户可以正常使用,防止系统雪崩。


接口限流的常用算法

  1. 计数器法

    计数器法也叫做 固定窗口算法。是限流算法里最简单也是最容易实现的一种算法。在一段时间间隔内(时间窗/时间区间),处理请求的最大数量固定,超过部分不做处理。

    以下举例基于redis的计数器实现

假设客户端访问时,每个客服端都有一个IP,把IP作为redis存储的键,每分钟可以访问10次或者是20次,这样做就是在规定的时间内限制访问
次数,超过这个次数的就直接拒绝访问。

class RedisNumController
{  
    #1分钟的访问次数不能超过100个。
    private $redis;
    private $maxNumber = 100;
    private $param;
    private $time = 60;
    
    public function __construct(Request $request)    
    {       
        $this->redis = RedisCon::getInstace();       
        $this->param = $request;    
    }
   
      
    public function index()    
    {        
        $ip = $this->param->getClientIp();  
              
        #给当前的请求IP加一个过期时间        
        $this->redis->expire($ip,$this->time); 
               
        #每次请求放入redis中自增             
        $num = $this->redis->incr($ip); 
               
         #判断是否超过我们限制的最大次数  
        if ($num > $this->maxNumber) {            
            exit('请求数据太过于频繁,请稍后再试!!!');        
        }
        echo '得到数据'.time();    
  }
}

计数器算法很简单,但是有一个严重的bug

一个恶意用户在0:59时瞬间发送了100个请求,然后再1:00时又瞬间发送了100个请求,那么这个用户在2秒内发送了200个请求。上面我们规定1分钟最多处理100个请求, 也就是每秒1.7个请求。用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过系统的承载能力,导致系统挂起或宕机。上面的问题,其实是因为我们统计的精度太低造成的。

 

2.令牌桶算法

实现原理:

  • 系统以固定的速率向桶中添加令牌;

  • 当有请求到来时,会尝试从桶中移除一个令牌;如果桶中有足够的令牌,则请求可以被处理;

  • 如果桶中没有令牌,那么请求将被拒绝;

  • 桶中的令牌数不能超过桶的容量,如果新生成的令牌超过了桶的容量,那么超出的令牌会被丢弃;

令牌桶算法的一个重要特性是,它能够应对突发流量。当桶中有足够的令牌时,可以一次性处理多个请求,这对于需要处理突发流量的应用场景非常有用。但是又不会无限制的增加处理速率导致压垮服务器,因为桶内令牌数量是有限制的。


token.png


代码实现

class TokenBucketLimiter 
{
    //桶的最大容量
    public static $threshold = 10;
    
    //桶内当前的令牌数量
     public static $count = 0;
     
     //令牌生成速率(每秒5次)
     public static $tokenRate = 5;
         
     //上次生成令牌的时间(毫秒)
     public static $lastRefillTime = round(microtime(true) * 1000);
          
     /**
      * 限流方法,返回true表示通过
     */
     public function limit() 
     {
       // 调用生成令牌方法
       $this->refillTokens();
             
       // 判断桶内是否还有令牌
       if ($count > 0) {
          $count--;
          return true;
       }
       return false;
     }
         
     /**
       * 生成令牌方法,计算并更新这段时间内生成的令牌数量
     */
     private function refillTokens() 
     {
       $currentTime = round(microtime(true) * 1000);
                    
       //计算这段时间内,需要生成的令牌数量
       $refillTokens = ($currentTime - lastRefillTime) * $tokenRate / 1000;
       $count = min($count + $refillTokens, $threshold);
       $lastRefillTime = $currentTime;
     }
          
}

优点

  • 可以处理突发流量:令牌桶算法可以处理突发流量。当桶满时,能够以最大速度处理请求。这对于需要处理突发流量的应用场景非常有用。
  • 限制平均速率:在长期运行中,数据的传输率会被限制在预定义的平均速率(即生成令牌的速率)。
  • 灵活性:与漏桶算法相比,令牌桶算法提供了更大的灵活性。例如,可以动态地调整生成令牌的速率。


缺点

  • 可能导致过载:如果令牌产生的速度过快,可能会导致大量的突发流量,这可能会使网络或服务过载。
  • 需要存储空间:令牌桶需要一定的存储空间来保存令牌,可能会导致内存资源的浪费。
  • 实现稍复杂:相比于计数器算法,令牌桶算法的实现稍微复杂一些。