作为应对高并发的手段之一,限流并不是什么新鲜话题。从Guava的Ratelimiter到Hystrix,还有Sentinel都可以作为限流工具。自适应限流一般的限流往往需要指定一个固定值(qps)作为限流开关的阈值。这个值一是靠经验判断,二是通过大量测试数据得到。但是,在流量激增、系统自动缩放或有人提交了一段有毒代码后,这个阈值可能会变得不合适。而且,一般的业务方都无法正确评估自身的容量,并设置一个合适的限流门槛。这时候,自适应限流就是为了解决这样的问题。限流阈值不需要手动指定,也不需要预估系统的容量,阈值可以随着系统相关指标的变化而变化。自适应限流算法借鉴了TCP拥塞算法,根据各种指标估计限流阈值,并不断调整。得到的大致效果如下:从图中可以看出,首先以降低的初始并发值发送请求,同时通过增加限流窗口来检测系统更高的并发量。一旦延迟增加到一定水平,它将返回到更小的限流窗口。循环继续探测并发限制,从而产生类似锯齿的计时功能。TCPVegas是一种主动调整cwnd的拥塞控制算法。它主要设置两个阈值alpha和beta,然后通过计算目标速率和实际速率的差值diff来调整cwnd,然后比较差值diff与alpha和beta的关系。.伪代码如下:diff=cwnd*(1-baseRTT/RTT)if(diff=beta)set:cwndcwnd=cwnd-1elseset:cwndcwnd=cwndwherebaseRTT指的是测量的最小往返时间,RTT指的是当前测量的往返时间,cwnd指的是当前的TCP窗口大小。通常在tcp中alpha会设置为2-3,beta会设置为4-6。这样cwnd就一直保持在平衡状态。netflix-concuurency-limitsconcuurency-limits是netflix推出的自适应限流组件。它借鉴了TCP相关的拥塞控制算法,主要根据请求延迟和直接受其影响的队列长度进行限流窗口的动态调整。alpha、beta和thresholdvegas算法在VegasLimit类中实现。先看初始化相关代码:privateininitialLimit=20;privateintmaxConcurrency=1000;privateMetricRegistryregistry=EmptyMetricRegistry.INSTANCE;privatedoublesmoothing=1.0;privateFunctionalphaFunc=(limit)->3*LOG10.apply(limit.intValue());privateFunctionbetaFunc=(limit)->6*LOG10.apply(limit.intValue());privateFunctionthresholdFunc=(limit)->LOG10.apply(limit.intValue());privateFunctionincreaseFunc=(limit)->limit+LOG10.apply(limit.intValue());privateFunctiondecreaseFunc=(limit)->limit-LOG10.apply(limit.intValue)());这里定义了一个初始化值initialLimit为20,定义了一个最大值maxConcurrency1000。其次是三个阈值函数alphaFunc、betaFunc和thresholdFunc。最后还有两个增减函数increaseFunc和decreaseFunc。函数都是根据当前的并发值限制来计算的。alphaFunc可以类比Vegas算法中的alpha,这里的实现是3*loglimit。当limit值从最初的20增加到最大1000时,对应的alpha从3.9增加到9。betaFunc可以类比Vegas算法中的beta,这里的实现是6*loglimit。当limit值从最初的20增加到最大1000时,对应的alpha从7.8增加到18。thresholdFunc是新加入的函数,代表一个相对初始的阈值。当小于这个值时,limit会采用更激进的增量算法。这里的实现是1倍的日志限制。当mit值从最初的20增加到最大1000时,对应的alpha从1.3增加到3。这三个函数值可以看作是确定了动态调整函数的四个区间范围。当变量queueSize=limit×(1?RTTnoLoad/RTTactual)落在这四个区间内时,应用不同的调整函数。变量queueSize中,变量为queueSize,计算方式为limit×(1-RTTnoLoad/RTTactual)。为什么这个算计,其实也只是稍微领悟而已。我们把系统处理请求的过程想象成一根水管。传入的请求是填充水管。当系统处理顺利时,请求不需要排队,直接通过水管。本次请求的RT最短,即RTTnoLoad;反之,当请求堆积起来,处理请求的时间就会变成:排队时间+最小处理时间,即RTTactual=inQueueTime+RTTnoLoad。显然,排队queue的长度是总的排队时间/每个请求的处理时间,queueSize=(limit*inQueueTime)/(inQueueTime+RTTnoLoad)=limit×(1?RTTnoLoad/RTTactual)。再举个栗子,因为假设当前的延迟是最好的延迟,那么自然不需要排队,即queueSize=0。并且假设当前延迟是最优延迟的两倍,可以认为处理能力减半,100个流量减半,即50个请求排队,queueSize=100*(1?1/2)=50。动态调整函数中最重要的是增加函数和减少函数。从初始化代码可知,增加函数increaseFunc实现为limit+loglimit,减少函数decreaseFunc实现为limit-loglimit。相对而言,增减幅度相对保守。看一下应用动态调整整数的相关代码:privateintupdateEstimatedLimit(longrtt,intinflight,booleandidDrop){finalintqueueSize=(int)Math.ceil(estimatedLimit*(1-(double)rtt_noload/rtt));doublenewLimit;//Treatanydrop(i.etimeout)asneedingtoreducethelimit//发现错误直接使用减量decreaseFuncif(didDrop){newLimit=decreaseFunc.apply(estimatedLimit);//Preventupwarddriftifnotclosetothelimit}elseif(inflight*2beta){newLimit=decreaseFunc.apply(estimatedLimit);//We'rewithinheswetspotsonothingtodo}else{return(int)estimatedLimit;}}newLimit=Math.max(1,Math.min(maxLimit,newLimit));newLimit=(1-smoothing)*estimatedLimit+smoothing*newLimit;if((int)newLimit!=(int)estimatedLimit&&LOG.isDebugEnabled()){LOG.debug("Newlimit={}minRtt={}mswinRtt={}msqueueSize={}",(int)newLimit,TimeUnit.NANOSECONDS.toMicros(rtt_noload)/1000.0,TimeUnit.NANOSECONDS.toMicros(rtt)/1000.0,queueSize);}estimatedLimit=newLimit;return(int)estimatedLimit;}动态调整函数规则如下:当变量queueSize