当前位置: 首页 > 后端技术 > Python

redis-限流实际应用

时间:2023-03-25 19:30:16 Python

为什么要限流首先让我们看一下为什么在系统架构设计中需要“限流”。旅游景点通常接待量最大,不可能让无限量的游客进入。比如故宫一天只卖8万张门票,超过8万的游客就不能买票进入,因为如果游客超过8万,景区的工作人员可能会忙不过来,景区人满为患也会影响游客的体验和心情,会存在安全隐患;只卖N张票是一种限流手段。软件架构中的业务流量限制类似。当系统资源不够时,处理大量的请求是不够的。为了保证服务的正常运行,根据规则,系统会直接拒绝多余的请求,达到限流的效果;不知道你有没有注意到。比如双11,刚过12点,一些客户的网页或者APP会提示下单失败,有的已经被限流了。常见的限流算法计数方式,顾名思义就是来一个记一个。比如我一分钟只能处理1000个请求,那么我们可以设置一个计数器,一个请求incr+1,当一分钟内的数大于等于1000后,就不处理了。伪代码如下$redis=newRedis();$redis->connect('127.0.0.1',6379);$rate_limit=1000;//限定次数$rate_seconds=60;//限制时间$redis_key="redis_limit";$count=$redis->get($redis_key);if($count>=$rate_limit){//判断60秒内请求次数是否达到上限//直接返回,不处理请求return}$redis->incr($redis_key,1);//请求计数$redis->expire($redis,$rate_seconds);//设置过期时间60s//做业务逻辑处理。......这种统计方式比较简单快速,但是有一个很大的缺点,就是请求的访问不一定很顺畅。如果0点59分过来1000个请求,1点01分已经是下一个窗口,又来了1000个请求,但实际上三秒内来了2000个请求,已经超过了我们目前的限制。所以不推荐这种方法。滑动窗口算法也拿上面的例子来说,一分钟分为6份,每份10秒;每隔10秒,我们的时间窗口就会向右滑动一格,每一格都有一个独立的计数器。在时间窗口内计算数量可以解决计数器方法中的问题,滑动窗口内的格子越多,限流统计就越准确。具体可以参考下图,比较清楚。伪代码实现了如下函数api_limit($scene,$period,$maxCount){$redis=newRedis();$redis->connect('127.0.0.1',6379);$key=sprintf('hist:%s',$scene);//限流场景唯一标识$now=msectime();//毫秒时间戳,这样比较准确$pipe=$redis->multi(Redis::PIPELINE);//使用管道提高性能$pipe->zadd($key,$now,$now);//value和score都使用毫秒时间戳$pipe->zremrangebyscore($key,0,$now-$period);//去掉时间窗之前的行为记录,剩下的就是$pipe->zcard($key);//获取窗口中的行为数$pipe->expire($key,$period/1000+1);//在过期时间上加一秒$replies=$pipe->exec();返回$replies[2]<=$maxCount;//$replies[2]是zcard返回的数如果zcard结果大于maxCount,则不处理结果}for($i=0;$i<20;$i++){//测试是否是限流实现代码var_dump(isActionAllowed("uniq_scene",60*1000,5));//执行可以发现只传了前5次}//返回当前毫秒时间戳functionmsectime(){list($msec,$sec)=explode('',microtime());$msectime=(float)sprintf('%.0f',(floatval($msec)+floatval($sec))*1000);返回$毫秒;}这段代码还是略显然它很复杂,需要读者花一定的时间细细琢磨。总体思路是:当每个行为到来时,维护一个时间窗口。清除时间窗口外的所有记录,只保留窗口内的记录。因为这些连续的Redis操作都是针对同一个key,所以使用pipeline可以显着提高Redis的访问效率。但是这个方案也有缺点,因为需要记录时间窗口内的所有行为记录。如果量很大,比如限制60s内操作的参数不超过100w次,就不适合做这样的限流,因为会消耗大量的存储空间。后面还有漏桶算法和令牌桶算法。由于他们各自的实现比较复杂,所以准备另开一篇文章单独介绍。