常见的限流算法有:计数器、漏桶、令牌桶。计数器(时间窗)原理:记录每一次请求,在设置的限流时间窗内判断请求数是否大于限制。限流时要注意避免边界问题,滑动时间窗的方法可以很好的解决这个问题。使用Redis排序集合实现并使用管道加速。思路:假设在$period秒内,一个用户只能访问$maxCount次。用户ID作为键,毫秒时间戳作为分数和值。一个请求进入并加入有序集合——zadd去掉时间窗口之前的行为记录,剩下的在时间窗口内——zremrangebyscore更新过期时间——expire获取窗口元素个数——zcard判断窗口是否元素个数大于最大请求限制(maxCount),若大于等于则拒绝请求,若小于则接受请求。函数isActionAllowed($userId,$action,$period,$maxCount){$redis=newRedis();$redis->connect('127.0.0.1',6379);$key=sprintf('hist:%s:%s',$userId,$action);$现在=毫秒时间();#毫秒时间戳$pipe=$redis->multi(Redis::PIPELINE);//使用管道提高性能$pipe->zadd($key,$now,$now);//value和score都使用毫秒时间戳$pipe->zremrangebyscore($key,0,$now-$period*1000);//去掉时间窗前的行为记录,剩下的就是时间窗内的$pipe->zcard($key);//获取窗口中的行为数$pipe->expire($key,$period+1);//再加一秒过期$replies=$pipe->exec();return$replies[2]<=$maxCount;}//执行($i=0;$i<20;$i++){var_dump(isActionAllowed("110","reply",60,5));//执行可以发现只传递了前5次}//返回当前毫秒时间戳functionmsectime(){list($msec,$sec)=explode('',microtime());$msectime=(float)sprintf('%.0f',(floatval($msec)+floatval($sec))*1000);返回$毫秒;}漏斗算法原理:漏桶算法的思想很简单。水(请求)先进入漏桶,漏桶以一定的速度出水(接口有响应速度)。当进水速度过快时,会直接溢出(访问频率超过接口响应速度),然后拒绝请求。可见,漏桶算法可以强制限制数据传输速率。使用Redis-Cell模块实现Redis4.0提供了一个限流的Redis模块redis-cell。该模块提供漏斗算法,并提供原子限流指令。这个模块只有一条指令cl.throttle,我们来看看它的参数和返回值>cl.throttletom:reply14306011)(integer)0#0表示允许,1表示拒绝2)(integer)15#漏斗容量capacity3)(integer)14#漏斗剩余空间left_quota4)(integer)-1#如果被拒绝,需要多长时间重试,以秒为单位5)(integer)2#多长时间后,漏斗全空,单元二令牌桶算法原理:令牌桶算法(TokenBucket)和LeakyBucket是作用相同方向相反的算法,比较容易理解。随着时间的推移,系统会以恒定的1/QPS间隔向桶中添加Token(如果QPS=100,则间隔为10ms)(想象有一个水龙头在不断往漏洞中加水),如果桶满了没有了。当有新的请求过来时,会拿走一个Token,如果没有Token拿走,就会阻塞或拒绝服务。使用RedisString+定时任务,实现利用定时任务不断增加token个数,Redis->incr(),自增1,当token桶满后不再增加;一个请求消费一个token,Redis->decr(),减一,没有token时拒绝请求。一旦需要提高速率,可以根据需要提高放入桶中令牌的速率。参考https://learnku.com/articles/...
