time/rate类库是基于令牌桶算法实现的限流功能。前面说令牌桶算法的原理是系统会以一个恒定的速率往桶里放入令牌,那么桶就有一个固定的大小,往桶中放入令牌的速率也是恒定的,并且允许突发流量。查看文档发现一个函数:

funcNewLimiter(rLimit,bint)*Limiter
newLimiter返回一个新的限制器,它允许事件的速率达到r,并允许最多突发b个令牌。也就是说Limter限制时间的发生频率,但这个桶一开始容量就为b,并且装满b个令牌(令牌池中最多有b个令牌,所以一次最多只能允许b个事件发生,一个事件花费掉一个令牌),然后每一个单位时间间隔(默认1s)往桶里放入r个令牌。
limter:=rate.NewLimiter(10,5)
上面的例子表示,令牌桶的容量为5,并且每一秒中就往桶里放入10个令牌。细心的读者都会发现函数NewLimiter第一个参数是Limit类型,可以看源码就会发现Limit实际上就是float64的别名。
//Limitdefinesthemaximumfrequencyofsomeevents.//Limitisrepresentedasnumberofeventspersecond.//AzeroLimitallowsnoevents.typeLimitfloat64
限流器还可以指定往桶里放入令牌的时间间隔,实现方式如下:
limter:=rate.NewLimiter(rate.Every(100*time.Millisecond),5)
这两个例子的效果是一样的,使用第一种方式不会出现在每一秒间隔一下子放入10个令牌,也是均匀分散在100ms的间隔放入令牌。rate.Limiter提供了三类方法用来限速:
Allow/AllowNWait/WaitNReserve/ReserveN
下面对比这三类限流方式的使用方式和适用场景。先看第一类方法:
func(lim*Limiter)Allow()boolfunc(lim*Limiter)AllowN(nowtime.Time,nint)bool
Allow 是AllowN(time.Now(), 1)的简化方法。那么重点就在方法 AllowN上了,API的解释有点抽象,说得云里雾里的,可以看看下面的API文档解释:
AllowNreportswhetherneventsmayhappenattimenow.Usethismethodifyouintendtodrop/skipeventsthatexceedtheratelimit.OtherwiseuseReserveorWait.
实际上就是为了说,方法 AllowN在指定的时间时是否可以从令牌桶中取出N个令牌。也就意味着可以限定N个事件是否可以在指定的时间同时发生。这个两个方法是无阻塞,也就是说一旦不满足,就会跳过,不会等待令牌数量足够才执行。
也就是文档中的第二行解释,如果打算丢失或跳过超出速率限制的时间,那么久请使用该方法。比如使用之前实例化好的限流器,在某一个时刻,服务器同时收到超过了8个请求,如果令牌桶内令牌小于8个,那么这8个请求就会被丢弃。一个小示例:
funcAllowDemo(){limter:=rate.NewLimiter(rate.Every(200*time.Millisecond),5)i:=0for{i++iflimter.Allow(){fmt.Println(i,"====Allow======",time.Now())}else{fmt.Println(i,"====Disallow======",time.Now())}time.Sleep(80*time.Millisecond)ifi==15{return}}}
执行结果:
1====Allow======2019-12-1415:54:09.9852178+0800CSTm=+0.0059980012====Allow======2019-12-1415:54:10.1012231+0800CSTm=+0.1220033013====Allow======2019-12-1415:54:10.1823056+0800CSTm=+0.2030858014====Allow======2019-12-1415:54:10.263238+0800CSTm=+0.2840182015====Allow======2019-12-1415:54:10.344224+0800CSTm=+0.3650042016====Allow======2019-12-1415:54:10.4242458+0800CSTm=+0.4450260017====Allow======2019-12-1415:54:10.5043101+0800CSTm=+0.5250903018====Allow======2019-12-1415:54:10.5852232+0800CSTm=+0.6060034019====Disallow======2019-12-1415:54:10.6662181+0800CSTm=+0.68699830110====Disallow======2019-12-1415:54:10.7462189+0800CSTm=+0.76699910111====Allow======2019-12-1415:54:10.8272182+0800CSTm=+0.84799840112====Disallow======2019-12-1415:54:10.9072192+0800CSTm=+0.92799940113====Allow======2019-12-1415:54:10.9872224+0800CSTm=+1.00800260114====Disallow======2019-12-1415:54:11.0672253+0800CSTm=+1.08800550115====Disallow======2019-12-1415:54:11.1472946+0800CSTm=+1.168074801
第二类方法:因为ReserveN比较复杂,第二类先说WaitN。
func(lim*Limiter)Wait(ctxcontext.Context)(errerror)func(lim*Limiter)WaitN(ctxcontext.Context,nint)(errerror)
类似Wait 是WaitN(ctx, 1)的简化方法。与AllowN不同的是WaitN会阻塞,如果令牌桶内的令牌数不足N个,WaitN会阻塞一段时间,阻塞时间的时长可以用第一个参数ctx进行设置,把 context 实例为context.WithDeadline或context.WithTimeout进行制定阻塞的时长。
funcWaitNDemo(){limter:=rate.NewLimiter(10,5)i:=0for{i++ctx,canle:=context.WithTimeout(context.Background(),400*time.Millisecond)ifi==6{//取消执行canle()}err:=limter.WaitN(ctx,4)iferr!=nil{fmt.Println(err)continue}fmt.Println(i,",执行:",time.Now())ifi==10{return}}}
执行结果:
1 ,执行:2019-12-14 15:45:15.538539 +0800 CST m=+0.0110234012 ,执行:2019-12-14 15:45:15.8395195 +0800 CST m=+0.3120039013 ,执行:2019-12-14 15:45:16.2396051 +0800 CST m=+0.7120895014 ,执行:2019-12-14 15:45:16.6395169 +0800 CST m=+1.1120013015 ,执行:2019-12-14 15:45:17.0385893 +0800 CST m=+1.511073701contextcanceled7 ,执行:2019-12-14 15:45:17.440514 +0800 CST m=+1.9129984018 ,执行:2019-12-14 15:45:17.8405152 +0800 CST m=+2.3129996019 ,执行:2019-12-14 15:45:18.2405402 +0800 CST m=+2.71302460110 ,执行:2019-12-14 15:45:18.6405179 +0800 CST m=+3.113002301
适用于允许阻塞等待的场景,比如消费消息队列的消息,可以限定最大的消费速率,过大了就会被限流避免消费者负载过高。
第三类方法:
func(lim*Limiter)Reserve()*Reservationfunc(lim*Limiter)ReserveN(nowtime.Time,nint)*Reservation
与之前的两类方法不同的是Reserve/ReserveN返回了Reservation实例。Reservation在API文档中有5个方法:
func(r*Reservation)Cancel()//相当于CancelAt(time.Now())func(r*Reservation)CancelAt(nowtime.Time)func(r*Reservation)Delay()time.Duration//相当于DelayFrom(time.Now())func(r*Reservation)DelayFrom(nowtime.Time)time.Durationfunc(r*Reservation)OK()bool
通过这5个方法可以让开发者根据业务场景进行操作,相比前两类的自动化,这样的操作显得复杂多了。通过一个小示例来学习Reserve/ReserveN:
funcReserveNDemo(){limter:=rate.NewLimiter(10,5)i:=0for{i++reserve:=limter.ReserveN(time.Now(),4)//如果为flase说明拿不到指定数量的令牌,比如需要的令牌数大于令牌桶容量的场景if!reserve.OK(){return}ts:=reserve.Delay()time.Sleep(ts)fmt.Println("执行:",time.Now(),ts)ifi==10{return}}}
执行结果:
执行:2019-12-14 16:22:26.6446468 +0800 CST m=+0.008000201 0s执行:2019-12-14 16:22:26.9466454 +0800 CST m=+0.309998801 247.999299ms执行:2019-12-14 16:22:27.3446473 +0800 CST m=+0.708000701 398.001399ms执行:2019-12-14 16:22:27.7456488 +0800 CST m=+1.109002201 399.999499ms执行:2019-12-14 16:22:28.1456465 +0800 CST m=+1.508999901 398.997999ms执行:2019-12-14 16:22:28.5456457 +0800 CST m=+1.908999101 399.0003ms执行:2019-12-14 16:22:28.9446482 +0800 CST m=+2.308001601 399.001099ms执行:2019-12-14 16:22:29.3446524 +0800 CST m=+2.708005801 399.998599ms执行:2019-12-14 16:22:29.7446514 +0800 CST m=+3.108004801 399.9944ms执行:2019-12-14 16:22:30.1446475 +0800 CST m=+3.508000901 399.9954ms
如果在执行Delay()之前操作Cancel()那么返回的时间间隔就会为0,意味着可以立即执行操作,不进行限流。
funcReserveNDemo2(){limter:=rate.NewLimiter(5,5)i:=0for{i++reserve:=limter.ReserveN(time.Now(),4)//如果为flase说明拿不到指定数量的令牌,比如需要的令牌数大于令牌桶容量的场景if!reserve.OK(){return}ifi==6||i==5{reserve.Cancel()}ts:=reserve.Delay()time.Sleep(ts)fmt.Println(i,"执行:",time.Now(),ts)ifi==10{return}}}
执行结果:
1 执行:2019-12-14 16:25:45.7974857 +0800 CST m=+0.007005901 0s2 执行:2019-12-14 16:25:46.3985135 +0800 CST m=+0.608033701 552.0048ms3 执行:2019-12-14 16:25:47.1984796 +0800 CST m=+1.407999801 798.9722ms4 执行:2019-12-14 16:25:47.9975269 +0800 CST m=+2.207047101 799.0061ms5 执行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 799.9588ms6 执行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 0s7 执行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 0s8 执行:2019-12-14 16:25:49.5984782 +0800 CST m=+3.807998401 798.0054ms9 执行:2019-12-14 16:25:50.3984779 +0800 CST m=+4.607998101 799.0075ms10执行:2019-12-14 16:25:51.1995131 +0800 CST m=+5.409033301 799.0078ms
看到这里time/rate的限流方式已经完成,除了上述的三类限流方式,time/rate还提供了动态调整限流器参数的功能。相关API如下:
func(lim*Limiter)SetBurst(newBurstint)//相当于SetBurstAt(time.Now(),newBurst).func(lim*Limiter)SetBurstAt(nowtime.Time,newBurstint)//重设令牌桶的容量func(lim*Limiter)SetLimit(newLimitLimit)//相当于SetLimitAt(time.Now(),newLimit)func(lim*Limiter)SetLimitAt(nowtime.Time,newLimitLimit)//重设放入令牌的速率
这四个方法可以让程序根据自身的状态动态的调整令牌桶速率和令牌桶容量。
结尾
通过上述一系列讲解,相信大家对各个限流的应用场景和优缺点也有了大致的掌握,希望在日常开发中有所帮助。限流仅仅是整个服务治理中的一个小环节,需要与多种技术结合使用,才可以更好的提升服务的稳定性的同时提高用户体验。
原文:https://mp.weixin.qq.com/s/HTQoAo1hVNBn7sNOveReCA
上一篇:ls是什么意思 ls比较污的意思
下一篇:杏色是什么颜色 深杏色标准图片