利用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,客户端根据自身情况是打回给前台错误还是定时重试
接着我们再看一段代码示例:
@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秒钟就成功了 。我们可以捋一捋这个逻辑:
推荐阅读
- 判断因果关系的向量时钟算法
- Oracle用户进程和后台进程详解
- 各种各样加密算法的js库,安全加密不再愁——crypto-js
- 什么是青砖茶,青砖茶的特点是什么
- Go垃圾回收之三色标记算法
- CentOS 7安装TCP BBR拥塞算法
- 神奇的数据恢复算法
- 史上最详细的Linux网卡ifcfg-eth0配置详解
- SEO算法演变与破解
- 什么是搜索引擎本地算法,它对SEO有那些影响?