- RateLimiter是基于令牌桶算法实现的一个多线程限流器,它可以将请求均匀的进行处理,当然他并不是一个分布式限流器,只是对单机进行限流。它可以应用在定时拉取接口数。 通过aop、filter、Interceptor 等都可以达到限流效果。
原理特别简单、轻量。引入guava包即可。
package com.ratelimiter; import com.google.common.util.concurrent.RateLimiter; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; /** * RateLimiter是基于令牌桶算法实现的一个多线程限流器,它可以将请求均匀的进行处理,当然他并不是一个分布式限流器,只是对单机进行限流。它可以应用在定时拉取接口数据, * * @version 1.0 * @date 2020-06-11 10:08 **/ public class RateMain { public static void main(String[] args) throws InterruptedException { long l = MILLISECONDS.toMicros(1L); long l1 = MILLISECONDS.toMillis(1L); long l2 = SECONDS.toMicros(1L); System.out.println("RateMain.main = " + l + "====" + l1 + "====" + l2); System.out.println("RateMain.main jdk8 新特性无穷大 = " + ((1.0 / 0.0) > 9.99)); RateLimiter rateLimiter = RateLimiter.create(1000); MILLISECONDS.sleep(1000L); // sleep 1s, 那么会导致初始化10个令牌 int i = 0; do { long start = System.currentTimeMillis(); boolean b = rateLimiter.tryAcquire(); // double b = rateLimiter.acquire(); // 取的令牌,返回获取令牌的耗时。 System.out.print(System.currentTimeMillis() - start); System.out.println("第" + (++i) + "条, 获取令牌:" + b); if (i > 50) { break; } } while (true); } }
原理
首先先讲一下令牌桶的原理,每隔一段时间生产一个令牌放入桶里,请求在执行时需要拿到令牌才可以执行,如果拿不到令牌将等待令牌产生,一个生产者,多个消费者。
但是这样的令牌桶有一个问题,如果CPU负载过高,生产令牌的线程没有获取到时间片生产令牌,那么限制的流量将会比设定值更低。
可能是出于这个原因,guava并没有这样做,而是一个惰性生产令牌:
每次请求令牌时,通过当前时间和下次产生令牌时间的差值计算出现在有多少个令牌。
storedPermits: 已有令牌数量
nextFreeTicketMicros: 下次产生令牌的时间点
stableIntervalMicros: 产生一个令牌需要的时间,例如10qps, = 1000/10 = 100ms会产生一个令牌
例如:获取令牌时,当前时间比下次产生令牌时间大,
会计算出令牌存储[(now-nextFreeTicketMicros)/stableIntervalMicros],如果令牌不够,则让线程sleep;
每次新生产令牌,下次产生令牌的时间点更新成当前时间时间(有等待,需要加上等待时间)。
断点进去就可以了。