详解限流算法,图示漏桶算法与令牌桶算法( 二 )

利用RateLimiter.create这个构造方法可以指定每秒向桶中放几个令牌,比方说上面的代码create(5),那么每秒放置5个令牌,即200ms会向令牌桶中放置一个令牌 。这边代码写了一条线程模拟实际场景,拿到令牌那么就能执行下面逻辑,看一下代码执行结果:
RateLimiterThread-0获取到了令牌,时间 = 2019-08-25 20:58:53RateLimiterThread-23获取到了令牌,时间 = 2019-08-25 20:58:54RateLimiterThread-21获取到了令牌,时间 = 2019-08-25 20:58:54RateLimiterThread-19获取到了令牌,时间 = 2019-08-25 20:58:54RateLimiterThread-17获取到了令牌,时间 = 2019-08-25 20:58:54RateLimiterThread-13获取到了令牌,时间 = 2019-08-25 20:58:54RateLimiterThread-9获取到了令牌,时间 = 2019-08-25 20:58:55RateLimiterThread-15获取到了令牌,时间 = 2019-08-25 20:58:55RateLimiterThread-5获取到了令牌,时间 = 2019-08-25 20:58:55RateLimiterThread-1获取到了令牌,时间 = 2019-08-25 20:58:55RateLimiterThread-11获取到了令牌,时间 = 2019-08-25 20:58:55RateLimiterThread-7获取到了令牌,时间 = 2019-08-25 20:58:56RateLimiterThread-3获取到了令牌,时间 = 2019-08-25 20:58:56RateLimiterThread-4获取到了令牌,时间 = 2019-08-25 20:58:56RateLimiterThread-8获取到了令牌,时间 = 2019-08-25 20:58:56RateLimiterThread-12获取到了令牌,时间 = 2019-08-25 20:58:56RateLimiterThread-16获取到了令牌,时间 = 2019-08-25 20:58:57RateLimiterThread-20获取到了令牌,时间 = 2019-08-25 20:58:57RateLimiterThread-24获取到了令牌,时间 = 2019-08-25 20:58:57RateLimiterThread-2获取到了令牌,时间 = 2019-08-25 20:58:57RateLimiterThread-6获取到了令牌,时间 = 2019-08-25 20:58:57RateLimiterThread-10获取到了令牌,时间 = 2019-08-25 20:58:58RateLimiterThread-14获取到了令牌,时间 = 2019-08-25 20:58:58RateLimiterThread-18获取到了令牌,时间 = 2019-08-25 20:58:58RateLimiterThread-22获取到了令牌,时间 = 2019-08-25 20:58:58看到,非常标准,在每次消耗一个令牌的情况下,RateLimiter可以保证每一秒内最多只有5个线程获取到令牌,使用这种方式可以很好的做单机对请求的QPS数控制 。
至于为什么2019-08-25 20:58:53这个时间点只有1条线程获取到了令牌而不是有5条线程获取到令牌,因为RateLimiter是按照秒计数的,可能第一个线程是2019-08-25 20:58:53.999秒来的,算在2019-08-25 20:58:53这一秒内;下一个线程2019-08-25 20:58:54.001秒来,自然就算到2019-08-25 20:58:54这一秒去了 。
上面的写法是RateLimiter最常用的写法,注意:

  • acquire是阻塞的且会一直等待到获取令牌为止,它有一个返回值为double型,意思是从阻塞开始到获取到令牌的等待时间,单位为秒
  • tryAcquire是另外一个方法,它可以指定超时时间,返回值为boolean型,即假设线程等待了指定时间后仍然没有获取到令牌,那么就会返回给客户端false,客户端根据自身情况是打回给前台错误还是定时重试
RateLimiter预消费处理请求,每次来一个请求就acquire一把是RateLimiter最常见的用法,但是我们看acquire还有个acquire(int permits)的重载方法,即允许每次获取多个令牌数 。这也是有可能的,请求数是一个大维度每次扣减1,有可能服务器按照字节数来进行限流,例如每秒最多处理10000字节的数据,那每次扣减的就不止1了 。
接着我们再看一段代码示例:
@Testpublic void testRateLimiter2() {RateLimiter rateLimiter = RateLimiter.create(1);System.out.println("获取1个令牌开始,时间为" + FORMATTER.format(new Date()));double cost = rateLimiter.acquire(1);System.out.println("获取1个令牌结束,时间为" + FORMATTER.format(new Date()) + ", 耗时" + cost + "ms");System.out.println("获取5个令牌开始,时间为" + FORMATTER.format(new Date()));cost = rateLimiter.acquire(5);System.out.println("获取5个令牌结束,时间为" + FORMATTER.format(new Date()) + ", 耗时" + cost + "ms");System.out.println("获取3个令牌开始,时间为" + FORMATTER.format(new Date()));cost = rateLimiter.acquire(3);System.out.println("获取3个令牌结束,时间为" + FORMATTER.format(new Date()) + ", 耗时" + cost + "ms");}代码运行结果为:
获取1个令牌开始,时间为2019-08-25 21:21:09.973获取1个令牌结束,时间为2019-08-25 21:21:09.976, 耗时0.0ms获取5个令牌开始,时间为2019-08-25 21:21:09.976获取5个令牌结束,时间为2019-08-25 21:21:10.974, 耗时0.997237ms获取3个令牌开始,时间为2019-08-25 21:21:10.976获取3个令牌结束,时间为2019-08-25 21:21:15.974, 耗时4.996529ms看到这就是标题所说的预消费能力,也是RateLimiter中允许一定程度突发流量的实现方式 。第二次需要获取5个令牌,指定的是每秒放1个令牌到桶中,我们发现实际上并没有等5秒钟等桶中积累了5个令牌才能让第二次acquire成功,而是直接等了1秒钟就成功了 。我们可以捋一捋这个逻辑:


推荐阅读