文档

java体系技术文档


RateLimiter限流

<h2>前言</h2> <p>在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流</p> <ul> <li>缓存 缓存的目的是提升系统访问速度和增大系统处理容量</li> <li>降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开</li> <li>限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理</li> </ul> <h2>常用限流算法</h2> <h3>漏铜算法</h3> <blockquote> <p>漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。</p> </blockquote> <p><img src="https://s1.ax1x.com/2020/03/18/8w43cT.png" alt="漏桶算法" /> 漏桶的出水速度是恒定的,那么意味着如果瞬时大流量的话,将有大部分请求被丢弃掉(也就是所谓的溢出)。</p> <h3>令牌桶算法</h3> <blockquote> <p>对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。如图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。</p> </blockquote> <p><img src="https://s1.ax1x.com/2020/03/18/8w48jU.png" alt="令牌桶算法" /> 生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,而且拿令牌的过程并不是消耗很大的事情。而且令牌桶还支持预消费。</p> <h3>RateLimiter使用</h3> <p>先来看一段代码:</p> <pre><code class="language-java">public void testAcquire() { RateLimiter limiter = RateLimiter.create(1); for(int i = 1; i &lt; 10; i = i + 2 ) { double waitTime = limiter.acquire(i); System.out.println("cutTime=" + System.currentTimeMillis() + " acq:" + i + " waitTime:" + waitTime); } }</code></pre> <p>执行结果:</p> <pre><code class="language-text">cutTime=1535439657427 acq:1 waitTime:0.0 cutTime=1535439658431 acq:3 waitTime:0.997045 cutTime=1535439661429 acq:5 waitTime:2.993028 cutTime=1535439666426 acq:7 waitTime:4.995625 cutTime=1535439673426 acq:9 waitTime:6.999223</code></pre> <p>首先通过RateLimiter.create(1);创建一个限流器,参数代表每秒生成的令牌数,通过limiter.acquire(i);来以阻塞的方式获取令牌,当然也可以通过tryAcquire(int permits, long timeout, TimeUnit unit)来设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回。</p> <p>从输出来看,RateLimiter支持预消费,比如在acquire(5)时,等待时间是3秒,是上一个获取令牌时预消费了3个两排,固需要等待3*1秒,然后又预消费了5个令牌,以此类推</p> <p>RateLimiter通过限制后面请求的等待时间,来支持一定程度的突发请求(预消费),在使用过程中需要注意这一点</p>

页面列表

ITEM_HTML