redis是一个高性能的内存型数据库,redis的操作上不存在IO,所以可以利用redis来帮助我们完成应用。以下我们针对redis在限流方面的应用。
1、计数器限流:使用redis计数器(incr)和时间设定(expire 或 pexpire)在一定时间内限定操作
$key = "test"; //api应用eg: $key = 'user:1:api_count'; 等
$limitNum = 5;
$time = 10;
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$check = $redis->exist($key);
if($check){
$redis->incr($key);
$count = $redis->get($key);
if($count>=$limitNum){
echo "你被限流了....";
}
}else{
$redis->incr($key);
$redis->expire($key, $time);
}2、漏斗:利用zset结构实现,key代表userid_action(某个用户的某个动作),value(userid_action_time), score操作发生的时间, zremrangeByScore可以根据score移除不在时间窗口的元素,计算元素个数与阈值比较。
但是缺点在数量用户非常大时,存储空间占用非常大,并且整个过程的原子性无法保证,意味着要用锁来控制,但如果加锁失败,就要重试或者放弃,这回导致性能下降和影响用户体验,同时代码复杂度也升高了。
3、令牌桶:随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.
令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.
class TrafficToken{
private $redis;
private $queue;
private $max;
private $config;
public function __construct($config, $queue, $max){
$this->config = $config;
$this->queue = $queue;
$this->max = $max;
$this->redis = $this->connect();
}
public function connect(){
try{
$redis = new Redis();
$redis->connect($this->config['host'], $this->config['port']);
return $redis;
}catch(Exception $e){
throw new Exception($e->getMessage());
return false;
}
}
public function add($num=0){
$curnum = $this->redis->lSize($this->queue);
$max = $this->max;
$addNum = $max >= $curnum+$num ? $num : $max-$curnum;
if($addNum>0){
$token = array_file(0, $addNum, 1);
$this->redis->lPush($this->queue, ...$token);
return $addNum;
}
return false;
}
public function get(){
return $this->redis->rpop($this->queue) ? true : false;
}
public function reset(){
$this->redis->del($this->queue);
return $this->add($this->max);
}
}
/********************/
//队列名称
$queue = 'container';
//最大数值
$max = 5;//配置项
$config = [ 'host' => '127.0.0.1', 'port' => 6379];$obj = new TrafficShaper($config, $queue, $max);
//添加令牌$obj->reset();//消费令牌// for($i=0; $i<8; $i++){// var_dump($obj->get());// }//添加令牌$add_num = $obj->add(10);var_dump($add_num);4、布隆过滤器:布隆过滤器可以理解为一个不怎么精确的set结构,当你使用它的contains方法判断某个对象是否存在时,它可能会误判。但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。
class BloomFilter { private $_redis; private $_size; private $_hashCount; private $_key; const KEY_BLOOM = 'colourlife:customer:bloom:filter'; public function __construct($size, $hash_count) { $this->_size = $size; $this->_hashCount = $hash_count; $this->initRedis(); } public function add($item) { $index = 0; $pipe = $this->_redis->pipeline(); while ($index < $this->_hashCount) { $crc = $this->hash($item, $index); $pipe->setbit(self::KEY_BLOOM, $crc, 1); $index++; } $pipe->exec(); } public function has($item) { $index = 0; $pipe = $this->_redis->pipeline(); while ($index < $this->_hashCount) { $crc = $this->hash($item, $index); $pipe->getbit(self::KEY_BLOOM, $crc); $index++; } $result = $pipe->exec(); return !in_array(0, $result); } private function hash($item, $index) { return abs(crc32(md5('m' . $index . $item))) % $this->_size; } private function initRedis() { $this->_redis = new Redis(); $this->_redis->connect('127.0.0.1', 6379); }}function checkBloomFilter($mobile , $param1 = 108500000 , $param2 = 6) { //$key_bloom = 'colourlife:customer:bloom:filter:change:mobile'; $filter = new BloomFilter($param1, $param2); $result = $filter->has($mobile); //$result = $filter->add($mobile); return $result;}var_dump(checkBloomFilter('234345345'));以上就是对redis限流的简单应用,实际情况中远比这要复杂,以上仅供参考