Redis-Cell漏桶算法,实现api限流
漏桶(Leaky Bucket)算法思路
水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:
安装Redis-Cell
1.下载安装包 https://github.com/brandur/redis-cell/releases
2.找到redis的目录,新建一个文件夹extend
3.解压刚下载的文件;把libredis_cell.d和libredis_cell.so 复制到extend目录
4.给extend目录添加权限
sudo chmod 755 ./extend
5.修改redis配置文件
vim redis.conf
//增加一行配置来挂载redis-cell扩展
loadmodule /www/server/redis/extend/libredis_cell.so(扩展的路径,不同的环境路径不同)
6.打开redis-cli
//输入
CL.THROTTLE test 100 400 60 3
//执行成功则代表安装成功
cl.throttle 命令
Redis-Cell只提供一个命令
cl.throttle test 90 30 60 1
参数说明
1.test:key
2.初始化桶容量
3.30 漏斗的速率 60秒能漏出30个
4.60 时间
5.1 每次漏出数量
返回值说明
1.(integer) 0 #0表示允许,1表示拒绝
2.(integer) 16
3.(integer) 15 #漏斗容量
4.(integer) -1 #-1表示正常能放入,0表示已满,正数表示几秒后可放入数据
5.(integer) 2 #几秒后漏斗数据完全漏完
基于thinkphp5.2 实现对ip-api限流
LeakyBucket实现类
<?php
/**
* Created by PhpStorm.
* User: kun
* Date: 19-7-31
* Time: 下午2:16
*/
namespace app\common\server;
/**
* 基于redis-cell实现的一个漏桶操作类
* Class LeakyBucket
* @package app\common\server
*/
class LeakyBucket
{
protected $key = null;
protected $max_burst = null;
protected $tokens = null;
protected $seconds = null;
protected $apply = 1;
protected $redis_connect;
/**
* LeakyBucket constructor.
* @param $key string
* @param $max_burst int 初始桶数量
* @param $tokens int 速率
* @param $seconds int 时间
* @param int $apply 每次漏水数量
*/
public function __construct($key,$max_burst,$tokens,$seconds,$apply=1)
{
$this->redis_connect = redis_connect(8);
$this->key = $key;
$this->max_burst = $max_burst;
$this->tokens = $tokens;
$this->seconds = $seconds;
$this->apply = $apply;
}
/**
* 是否放行
* @return int 0 or 1 0:放行 1:拒绝
*/
public function isPass()
{
$rs = $this->redis_connect->rawCommand('CL.THROTTLE',$this->key,$this->max_burst,$this->tokens,$this->seconds,$this->apply);
return $rs[0];
}
}
LeakyBucket 中间件
<?php
/**
* Created by PhpStorm.
* User: kun
* Date: 19-7-31
* Time: 下午4:18
*/
namespace app\http\middleware;
use app\common\Traits\ApiResponse;
/**
* ip限流中间件
* Class LeakyBucket
* @package app\http\middleware
*/
class LeakyBucket
{
use ApiResponse;
public function handle($request, \Closure $next)
{
$ip = $request->ip(0,false);
$leakyBucketConfig = config('leaky_bucket.');
$server = new \app\common\server\LeakyBucket($ip,$leakyBucketConfig['max_burst'],$leakyBucketConfig['tokens'],$leakyBucketConfig['seconds']);
if ($server->isPass()){
$this->notFond('操作频繁,请稍后再试');
}
return $next($request);
}
}