GuavaRateLimiter。有什么错误吗?别人在增加系统的并发访问,你却在这里限制?我们都知道服务器资源是有限的。在外部网络环境中部署应用程序时,每个人都可以访问您的应用程序。如果访问量增加,您的服务器是否可以支持足够的用户访问?在系统访问高峰期,仅在代码层面提供系统并发,系统真的能支撑突发流量的冲击吗?显然,这是不可能的。如果有人允许你在不改变硬件配置的情况下提高系统性能,你可以说他是在做白日梦。简介限流器,顾名思义,就是限制流量。准确的说应该是流量控制。当然,控制流量也不是没有道理的。应该在电脑硬件可以承载的范围内,以免系统突然流量过大造成系统资源不足。筋疲力尽,最终导致系统宕机或崩溃,导致服务器上的所有应用程序挂掉。限流器是在保证应用程序能够正常提供服务的前提下,通过流量控制来保护服务的一种手段。当然,流量的合适阈值是多少?这可能需要根据实际的硬件配置和系统环境的其他相关参数,通过各种测试和验证才能知道……本文只讨论限流中的相关技术。在实际应用中,除了限流的作用,为了提高用户体验,限流器中使用的限流器还需要在流量超过时做出相应的应对策略,比如直接拒绝服务,将请求排队等,或降低服务质量。该处理方式既能给用户带来友好的体验,又能保证正常的服务。核心中有几个核心概念需要先了解一下:限流的目标对象:请求数、网络流量、用户访问次数……限流维度:时间、IP、用户限流实现层级:前端-端页面、WEB代理、服务接口...应用场景防止服务接口在短时间内涌入大量请求,导致服务器资源快速耗尽。当最终服务无法接入突发流量时,通过排队策略实现流量削峰,防止对服务器造成冲击。针对IP或用户控制对某个资源的访问次数针对高频接口,控制单位时间内允许的请求数。结合IP或者其他因素来防止爬虫和限流的方法。这里主要讨论后端根据请求量限流。限流是一个很广泛的例子,比如你在登录系统的时候,经常需要输入手机验证,动态码或者一些奇怪的验证方式来减少登录请求的频率。计数限流是根据数量来控制的,当达到设定的阈值时进行限流。固定窗口和滑动窗口就是通过这种方法实现的。固定窗口控制时间单位内允许的请求数。一旦达到阈值,将不再处理该请求的后续相关业务或请求快速失败并给出提示。比如我们配置允许的请求流量为10秒内1000个,那么1~9秒内的请求数为0,9~10秒内的请求数为1000,这样一秒内的请求数达到1000。当然,我们可以把时间单位分成更小的粒度,但是应该多小呢?问题:只能控制一个时间单位内的请求总数。当请求集中在一个小的时间范围内时,就达不到限流的效果。因此,这是一种粗粒度的限流方法。滑动窗口是用来解决固定窗口算法中存在的问题,通过滑动窗口的方法,将上述时间单元划分为多个细粒度的时间窗口,每个窗口都有自己独立的请求计数器,使得流控在时间单元可以均匀地实现在每个时间窗上,同时滑动的时间窗可以形成一个连续的时间间隔控制,而不是像一个固定的窗口那样只存在于两个时间尺度之间。例如时间单位为1s,每个时间窗为100ms,1秒内的10个时间窗可以是09:01:01.000~09:01:02.000,09:01:01.200~09:01:02.800..问题:滑动窗口的区间划分的越多,滑动窗口的滚动越流畅,限流统计越准确,但是需要更多的资源来保存窗口时间段的计数器,从而消耗系统资源。漏桶算法如果将请求视为一滴水,则将限流器视为底部开口的桶(漏桶)。漏桶算法其实就是当水滴(请求)先进入漏桶时,漏桶会以一定的速度放水。当进水速度过快时,会超过水桶可接受的容量。算法可以对数据传输速率施加限制。使用漏桶算法可以保证接口以恒定的速率处理请求,所以漏桶算法肯定不会出现临界问题。问题:当短时间内有大量突发请求时,即使服务器负载不高,每个请求也需要等待一段时间(水滴间隔)才能响应。令牌桶算法令牌桶算法会以恒定的速度将令牌放入桶中,如果需要处理请求,需要先从桶中取出令牌,当桶中没有令牌时,该服务将被拒绝。与“漏桶算法”相比,“令牌桶算法”不仅可以限制平均数据传输速率,还可以让它应对突然增加的流量(允许突发请求,只要有足够的令牌,它支持一次取多个令牌)。实现示例固定窗口publicclassFixedWindowLimiter{/***时间单位ms*/privatelongtimeUnit;/***时间单位内的阈值*/privatelonglimit;/***开始时间*/privatelongstartTime;/***计数器*/privatelongcount;publicFixedWindowLimiter(longtimeUnit,longlimit){this.timeUnit=timeUnit;this.limit=限制;}/****@return*/publicsynchronizedbooleanacquire(){longnow=System.currentTimeMillis();//开始if(startTime==0){startTime=now;数++;返回真;}//在一个时间单位内if(now-startTime<=timeUnit){count++;返回计数<=限制;}else{//超过时间单位,startTime=now;计数=0;返回真;}}GuavaRateLimiter令牌桶算法实现支持预热,支持突发流量配置说明permitsPerSecond单位时间生成令牌数warmupPeriodwarmupperiodunitwarmupperiodtimeunit创建RateLimiter,每秒发出6个令牌,平均间隔167ms,有3秒的预热期预热期后发行量TimeUnit.SECONDS//时间单位);使用RateLimiter@Testpublicvoidlimit()throwsInterruptedException{Stopwatchstopwatch=Stopwatch.createStarted();持久;对于(inti=0;i<100;i++){last=stopwatch.elapsed(TimeUnit.MILLISECONDS);rateLimiter.acquire();长持续时间=stopwatch.elapsed(TimeUnit.MILLISECONDS);System.out.println(String.format("%s时间,开始时间:%sms,间隔时间:%sms",i,duration,duration-last));if(i==20){//中间暂停5秒,看token申请的时间间隔变化TimeUnit.SECONDS.sleep(5);System.out.println("暂停5秒后...");longlast2=stopwatch.elapsed(TimeUnit.MILLISECONDS);rateLimiter.acquire(10);长持续时间2=stopwatch.elapsed(TimeUnit.毫秒);System.out.println(String.format("%stime,timefromstart:%sms,interval:%sms",i,duration2,duration2-last2));}}}结果:大约3秒后,第0次进入稳定期,第一次开始时间:0ms,间隔时间:0ms,第一次开始时间:483ms,第一次间隔时间:483ms第二次,从开始时间:926ms,间隔时间:443ms第三次,从开始时间:1334ms,间隔时间:408ms第四次,从开始时间:1703ms,间隔时间:369ms第五次,从开始时间:2036ms,间隔时间:第6次333ms,间隔时间:2333ms,间隔时间:297ms,第7次,间隔时间:2592ms,间隔时间:259ms,第8次,间隔时间:2815ms,间隔时间:223ms,第9次,从开始时间:2999ms,间隔:184ms第10次,从开始时间:3166ms,间隔:167ms第11次,从开始时间:3333ms,间隔:167ms第12次,从开始时间:3500ms,间隔:167ms第13次,从开始时间:3666ms,间隔时间:166ms...暂停5秒后...第20次,从开始时间:9842ms,间隔时间:0ms第21次,从开始时间:13009ms,间隔:3166ms22、开始时间:13176ms,间隔:167ms23、开始时间:13342ms,间隔:166ms24、开始时间:13510ms,间隔:第25次168ms,开始时间:13676ms,间隔时间:166ms第26次开始时间:13843ms间隔时间:167ms第27次开始时间:14009ms间隔时间:166ms长时间请求,限流器处于冷却状态。预热期获得代币的时间会比平滑期获得代币的时间长。随着代币的减少,获取单个代币的时间会逐渐变短,最终达到一个稳定值。acquireacquire是一个阻塞方法,阻塞方法会通过RateLimiter获取停止时间值tryAcquire非阻塞方法,快速返回令牌申请结果当你往下探索时,你会得到更多不可思议的知识。后面我们会有机会从源码和底层去了解这些技术和设计思路
