限流:限制一定时间内的访问次数,保证系统的可用性和稳定性。防止突然的访问激增导致系统响应缓慢或停机。场景:在php-fpm中,fpm开启的子进程数量是有限制的。当并发请求大于可用子进程数时,进程池无法分配多余的子进程来处理http请求,服务就会开始阻塞。导致nginx抛出502。了解了大概的概念,现在主要说说限流在单体架构下的使用。1.服务代理层限流nginx限流之HttpLimitRequest模块该模块可以指定session请求的次数,可以通过指定ip来限制请求频率。使用漏桶算法限制请求频率。示例:http{//会话状态存储在10m的名为“one”的区域中。该区域的平均查询限制为每秒1个请求limit_req_zone$binary_remote_addrzone=one:10mrate=1r/s;...server{...location/search/{//平均每秒请求不超过1个requestburst发送不超过5个query如果不需要在burstdelay内限制超额请求,应该使用nodelaylimit_reqzone=oneburst=5nodelay;}详细请参考nginx文档HttpLimitReqest模块这是nginx文档中关于限流小例子的摘录。nginx使用的漏桶算法限制了用户访问的频率。我们是通过百度和谷歌知道的。原来限流是基于算法实现的。下面介绍两种限流算法:实现限流的算法,漏桶算法,令牌桶算法,当然我们不仅要知道它是什么,还要知道它是为什么。1.漏桶算法漏桶算法:漏桶有一定的容量,漏桶就会漏水。当单位时间内注入的水量大于单位时间内流出的水量时。漏水的桶里积水越来越多。直到溢出,如果溢出,需要限流。算法说明:当前水量:上次容量-出水容量+注水量出水容量:(当前注水时间-上次注水时间)*出流量当“当前水量”>“桶容量”时,会溢出。否则正常,记录水量和注水时间。通过图片描述漏桶算法2.php+redis实现漏桶算法限流类AddBucketLimit.phpclassprotected$capacity=60;//受保护的桶的总容量$addNum=20;//每次注水保护的容量$rate=2;//漏水率保护$water_key="water_capacity";//缓存键public$redis;//使用redis缓存当前桶水量和上次注水时间publicfunction__construct(){$redis=new\Redis();$this->redis=$redis;$this->redis->connect('127.0.0.1',6379);}具体实现方法/***@param$api[字符串指定接口限流]*@param$addNum[int注水量]*@returnbool*/publicfunctionbucket($addNum,$api=''){$this->addNum=$addNum;//获取桶中最后一次注水时间列表($waterCapacity,$waterTime,$lastTime)=$this->getLastWater();//计算时间内流出的水量$lastWater=($lastTime-$waterTime)*$this->rate;//这次的水量$waterCapacity=$waterCapacity-$lastWater;//水量不能小于0$waterCapacity=($waterCapacity>=0)?$水容量:0;$waterTime=$lastTi我;//当前水量大于桶容量,overflow返回false存储水量和注水时间$this->setWater($waterCapacity,$waterTime);返回真;}else{$this->setWater($waterCapacity,$waterTime);返回假;}}/***@returnarray[$waterCapacity,$waterTime,$lastTime]*currentcapacityup第二次漏水的当前时间*/privatefunctiongetLastWater(){$water=$this->redis->get($this->water_key);如果($water){$water=json_decode($water,true);$waterCapacity=$water['water_capacity'];//最后一次容量$waterTime=$water['time'];//最后一次注水时间$lastTime=time();//本次注水时间}else{$this->redis->set($this->water_key,json_encode(['water_capacity'=>0,'time'=>time()]));$水容量=0;//最后容量$waterTime=time();//最后一次注水时间$lastTime=time();//这次注水时间}return[$waterCapacity,$waterTime,$lastTime];}/***@param$waterCapacity[int当前剩余容量]*@param$waterTime[int当前注水时间]*/privatefunctionsetWater($waterCapacity,$waterTime){$this->redis->set($this->water_key,json_encode(['water_capacity'=>$waterCapacity,'time'=>$waterTime]));}开始测试使用for+sleep功能模拟请求一般为2s。该方法通常是无限的。请求少于2秒。请求将被限制到第四次。require_once'BucketLimit.php';$bucket=newBucketLimit();for($i=1;$i<=100;$i++){//根据for+sleep函数,模拟请求正常为2s,方法通常是无限流睡眠(1);$data=$bucket->bucket(10);var_dump($data)."\n";}2.令牌桶算法令牌桶算法与漏桶算法正好相反。每次请求以指定的速率将令牌放入桶中时,都会从桶中取出一个令牌。Token一旦被消耗,流量就会被限制。优点:更改代币交付率很方便。用例hyperf令牌桶算法实现限流代码3.laravel框架中api限流app/Http/Kernel.phpprotected$middlewareGroups=['api'=>['throttle:60,1',//执行中间请求限制为每分钟60次],];源码分析判断是否设置api请求限速执行限速方法判断限速方法根据缓存key判断api设置时间单位内请求次数达到阈值达到请求阈值,并进行限速Injectcacheinstanceprotected$limiter;/***创建一个新的请求节流器。**@param\Illuminate\Cache\RateLimiter$limiter*@returnvoid*/publicfunction__construct(RateLimiter$limiter){$this->limiter=$limiter;}判断是否配置了限速/***处理传入的请求。**@param\Illuminate\Http\Request$request*@param\Closure$next*@paramint|string$maxAttempts*@paramfloat|int$decayMinutes*@paramstring$prefix*@return\Symfony\Component\HttpFoundation\响应**@throws\Illuminate\Http\Exceptions\ThrottleRequestsException*/publicfunctionhandle($request,Closure$next,$maxAttempts=60,$decayMinutes=1,$prefix=''){//判断用户是否限制频率if(is_string($maxAttempts)&&func_num_args()===3&&!is_null($limiter=$this->limiter->limiter($maxAttempts))){return$this->handleRequestUsingNamedLimiter($request,$next,$maxAttempts,$limiter);}}//执行频率限制判断参数为:return$this->handleRequest($request,//请求类$next,//中间件基类[(object)['key'=>$prefix.$this->resolveRequestSignature($request),//缓存键'maxAttempts'=>$this->resolveMaxAttempts($request,$maxAttempts),//获取频繁阈值'decayMinutes'=>$decayMinutes,'responseCallback'=>null,//存储回调响应],]);}判断是否达到阈值/***处理传入的请求。**@param\Illuminate\Http\Request$request*@param\Closure$next*@paramarray$limits*@return\Symfony\Component\HttpFoundation\Response**@throws\Illuminate\Http\Exceptions\ThrottleRequestsException*/protectedfunctionhandleRequest($request,Closure$next,array$limits){foreach($limitsas$limit){//判断速率是否达到阈值,返回truefalse该方法使用缓存实例取出缓存的keyif($this->limiter->tooManyAttempts($limit->key,$limit->maxAttempts)){throw$this->buildException($request,$limit->key,$limit->maxAttempts,$limit->响应回调);}//类似于redisvalue自增并设置过期时间$this->limiter->hit($limit->key,$limit->decayMinutes*60);}$response=$next($request);//将响应放入响应回调函数中foreach($limitsas$limit){$response=$this->addHeaders($response,$limit->maxAttempts,$this->calculateRemainingAttempts($limit->key,$limit->maxAttempts));}//返回响应return$response;}获取频率$this->limiter->tooManyAttempts方法/***确定给定的键是否被“访问”了太多次。**@paramstring$key*@paramint$maxAttempts*@returnbool*/publicfunctiontooManyAttempts($key,$maxAttempts){if($this->attempts($key)>=$maxAttempts){if($this->cache->has($key.':timer')){返回真;}$this->resetAttempts($key);}返回假;}该方法实现的原理:周期性的限制次数/time来限制请求频率。下面是我根据以上逻辑实现的这样一个类,仅供参考。类CurrentLimiting{受保护的$limit;受保护的$分钟;受保护的$redis;受保护的$密钥;/***CurrentLimiting构造函数。*@paramstring$api接口*@paramstring$ipip*@paramint$limit限制频率*@paramint$minutes分钟*/publicfunction__construct(string$api,string$ip,int$limit,int$minutes){$redis=new\Redis();$redis->connect('127.0.0.1','6379',3);$this->redis=$redis;$this->limit=$limit;$this->minutes=$minutes;$this->key=$ip.$api;}//获取请求次数publicfunctionattempts(){$count=$this->redis->get($this->key);返回is_null($count)?0:$计数;}/****@returnbool*/publicfunctionCurrentLimit(){$count=$this->attempts();如果($count>=$this->limit){返回false;}if($count==0){$this->redis->设置($this->key,0,$this->minutes*60);}//设置锁$this->redis->multi();$this->redis->watch();$this->redis->incr($this->key);返回真;}}
