一、什么是接口限流
那么什么是限流呢?顾名思义,限流就是限制流量,包括并发的流量和一定时间内的总流量,就像你宽带包了1个G的流量,用完了就没了,所以控制你的使用频率和单次使用的总消耗。
通过限流,我们可以很好地控制系统的qps,从而达到保护系统或者接口服务器稳定的目的。
二、常用的接口限流算法
1、计数器法
2、漏桶算法
3、令牌桶算法
今天主要使用令牌桶算法进行接口限流(基于laravel框架)
首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的(可用token数为0),token以一个固定的速率r往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。
令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.
代码实现:
1、通过定时任务,匀速的向令牌桶中添加令牌
<?php
namespace App\Console\Commands;
use App\Http\Controllers\Api\V1\TokenBucketController;
use Illuminate\Console\Command;
class CrontabAddTokens extends Command
{
/**
* 定时往令牌桶里添加令牌
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'CrontabTokens:add';
/**
* The console command description.
*
* @var string
*/
protected $description = '定时自动增加令牌数';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// 令牌桶容器
$queue = 'mycontainer';
// 最大令牌数
$max = 100;
// 每次时间间隔加入的令牌数
$token_num = 10;
// 时间间隔,最好是能被60整除的数,保证覆盖每一分钟内所有的时间
$time_step = 1;
// 执行次数
$exec_num = (int)(60/$time_step);
// 创建TokenBucketController对象
$tokenBucket = new TokenBucketController($queue, $max);
for($i = 0; $i < $exec_num; $i++){
$add_num = $tokenBucket->add($token_num);
echo '['.date('Y-m-d H:i:s').'] add token num:'.$add_num.PHP_EOL;
sleep($time_step);
}
}
}
2、创建一个TokenBucketController对象,用户添加和消费令牌以及一些初始化设置
<?php
namespace App\Http\Controllers\Api\V1;
use Illuminate\Support\Facades\Redis;
class TokenBucketController extends BaseController
{
private $_queue; // 令牌桶
private $_max; // 最大令牌数
/**
* 初始化
* @param Array $config redis连接设定
*/
public function __construct($queue, $max = 10){
$this->_queue = $queue;
$this->_max = $max;
}
/**
* 加入令牌
* @param Int $num 加入的令牌数量
* @return Int 加入的数量
*/
public function add($num=0)
{
// 当前剩余令牌数
$curnum = intval(Redis::Llen($this->_queue));
// 最大令牌数
$maxnum = intval($this->_max);
// 计算最大可加入的令牌数量,不能超过最大令牌数
$num = $maxnum >= $curnum + $num ? $num : $maxnum - $curnum;
// 加入令牌
if($num>0){
$token = array_fill(0, $num, 1);
Redis::lPush($this->_queue, ...$token);
return $num;
}
return 0;
}
/**
* 获取令牌
* @return Boolean
*/
public function get(){
return Redis::rPop($this->_queue)? true : false;
}
/**
* 重设令牌桶,填满令牌
*/
public function reset(){
Redis::del($this->_queue);
$this->add($this->_max);
}
}
3、模拟用户请求消费令牌
<?php
namespace App\Http\Controllers\Api\V1;
use App\Component\ApiReturn;
use App\Component\Logic\GoodsLogic;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class TokenConsumeController extends BaseController
{
public function consume()
{
$mycontainer = 'mycontainer';
//获取当前令牌桶状态,是否获取到令牌
$status = $this->getMycontainerStatus($mycontainer );
if($status){
//取到令牌,则查询数据
$query = (new GoodsLogic())->getInfo(['id' => 1]);
return ApiReturn::success($query);
}else{
//未取到令牌,则返回服务繁忙
return Response::json([
'msg' => '服务器繁忙,请稍后再试'
], 500); // Status code here
}
}
/**
* 获取令牌桶令牌状态
* @param string $queue
* @return bool
*/
public function getMycontainerStatus($queue = 'mycontainer')
{
// 令牌桶容器
// $queue = 'mycontainer';
// 创建TokenBucketController对象
$tokenBucket = new TokenBucketController($queue);
$status = $tokenBucket->get();
return $status;
}
}