php是如何对api接口做限流的 |
您所在的位置:网站首页 › 总流量的限制 › php是如何对api接口做限流的 |
什么是接口限流那么什么是限流呢?顾名思义,限流就是限制流量,包括并发的流量和一定时间内的总流量,就像你宽带包了1个G的流量,用完了就没了,所以控制你的使用频率和单次使用的总消耗。 通过限流,我们可以很好地控制系统的qps,从而达到保护系统或者接口服务器稳定的目的。 接口限流的常用算法计数器法计数器法是限流算法里最简单也是最容易实现的一种算法。 比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内,那么说明请求数过多; 如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置counter。 具体算法的示意图如下: 代码如下 class CounterDemo{ private $first_request_time; private $request_count = 0; //已请求的次数 public $limit = 100; //时间窗口内的最大请求数 public $interval = 60; //时间窗口 s public function __construct() { $this->first_request_time = time(); } public function grant(){ $now = time(); if($now first_request_time + $this->interval){ //时间窗口内 if($this->request_count limit) { $this->request_count++; return true; }else{ return false; } }else{ //超出前一个时间窗口后, 重置第一次请求时间和请求总次数 $this->first_request_time = $now; $this->request_count = 1; return true; } } } $m = new CounterDemo(); $n_success = 0; for($i=0; $i grant(); if($rt){ $n_success ++; } } echo '成功请求 '.$n_success.' 次';计数器算法很简单,但是有个严重的bug: # 漏桶算法 (Leaky Bucket)漏桶算法(Leaky Bucket): 平滑网络上的突发流量。使其整流为一个稳定的流量。 令牌桶算法比漏桶算法稍显复杂。首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的(可用token数为0),token以一个固定的速率r往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。 代码实现如下: class TokenBucketDemo{ private $last_req_time; //上次请求时间 public $capacity; //桶的容量 public $rate; //令牌放入的速度(个/秒) public $tokens; //当前可用令牌的数量 public function __construct(){ $this->last_req_time = time(); $this->capacity = 100; $this->rate = 20; $this->tokens = 100; //开始给100个令牌 } public function grant(){ $now = time(); $tokens = min($this->capacity,$this->tokens + ($now - $this->last_req_time) * $this->rate);// 计算桶里可用的令牌数 $this->tokens = $tokens; $this->last_req_time = $now; if($this->tokens tokens -= 1; return true; } } } $m = new TokenBucketDemo(); $n_success = 0; for($i=0; $i grant(); if($rt){ $n_success ++; } if($i > 0 && $i % 100 == 0){//每发起100次后暂停1s echo '已发送',$i,', 成功 ', $n_success,', sleep'.PHP_EOL; sleep(1); } } echo '成功请求 '.$n_success.' 次';我们可以使用redis的队列作为令牌桶容器使用,使用lPush(入队),rPop(出队),实现令牌加入与消耗的操作。TokenBucket.php _config = $config; $this->_queue = $queue; $this->_max = $max; $this->_redis = $this->connect(); } /** * 加入令牌 * @param Int $num 加入的令牌数量 * @return Int 加入的数量 */ public function add($num=0){ // 当前剩余令牌数 $curnum = intval($this->_redis->lSize($this->_queue)); // 最大令牌数 $maxnum = intval($this->_max); // 计算最大可加入的令牌数量,不能超过最大令牌数 $num = $maxnum>=$curnum+$num? $num : $maxnum-$curnum; // 加入令牌 if($num>0){ $token = array_fill(0, $num, 1); $this->_redis->lPush($this->_queue, ...$token); return $num; } return 0; } /** * 获取令牌 * @return Boolean */ public function get(){ return $this->_redis->rPop($this->_queue)? true : false; } /** * 重设令牌桶,填满令牌 */ public function reset(){ $this->_redis->delete($this->_queue); $this->add($this->_max); } /** * 创建redis连接 * @return Link */ private function connect(){ try{ $redis = new Redis(); $redis->connect($this->_config['host'],$this->_config['port'],$this->_config['timeout'],$this->_config['reserved'],$this->_config['retry_interval']); if(empty($this->_config['auth'])){ $redis->auth($this->_config['auth']); } $redis->select($this->_config['index']); }catch(RedisException $e){ throw new Exception($e->getMessage()); return false; } return $redis; } } ?>令牌的假如与消耗 'localhost', 'port' => 6379, 'index' => 0, 'auth' => '', 'timeout' => 1, 'reserved' => NULL, 'retry_interval' => 100, ); // 令牌桶容器 $queue = 'mycontainer'; // 最大令牌数 $max = 5; // 创建TrafficShaper对象 $tokenBucket = new TokenBucket($config, $queue, $max); // 重设令牌桶,填满令牌 $tokenBucket->reset(); // 循环获取令牌,令牌桶内只有5个令牌,因此最后3次获取失败 for($i=0; $iget()); } // 加入10个令牌,最大令牌为5,因此只能加入5个 $add_num = $tokenBucket->add(10); var_dump($add_num); // 循环获取令牌,令牌桶内只有5个令牌,因此最后1次获取失败 for($i=0; $iget()); } ?>本文转自:juejin.cn/post/7174952978791530533 本作品采用《CC 协议》,转载必须注明作者和本文链接 最美的不是下雨天,而是和你一起躲过的屋檐! |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |