redis 在限流中的应用小结

348 阅读2分钟

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限流的简单应用,实际情况中远比这要复杂,以上仅供参考