• 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;
每次新生产令牌,下次产生令牌的时间点更新成当前时间时间(有等待,需要加上等待时间)。

断点进去就可以了。