赞
踩
之前已经说过漏桶算法,请求过来之后,由漏桶来阻挡,最后将流量漏出去,达到限流的目的,削弱峰值流量对服务器的压力。
不过,漏桶算法真正在生产中用的并不是很多,更多的还是使用令牌桶算法。什么是令牌桶算法呢?
说的已经很清楚了么,令牌--桶。就是一个桶里面放了令牌(一个校验或者说是一个通行证),每次有请求过来,必须要从桶里拿到令牌(许可证),才允许通过。如果拿不到,那不好意思,你就不允许通过了。
令牌是哪里来的呢?当然是定时生成的啊,就好像桶的那个漏洞一样,一直在滴水,而这个令牌桶,也一直在往里面令牌,直到令牌桶满了,才会停止加(生成)令牌。
那令牌桶和漏桶有啥区别呢?都是通过一个桶进行限流,漏桶似乎更方便,而且避免了生成令牌这个步骤,不是更简单么?
这个也想了一下其中的区别,当然度娘的帮助更大!
简单的说下,漏桶算法,其实太匀速,太平缓了,对于一些突发状况很难进行处理。
比如,桶的大小为100,突然来了99个请求,然后又来了6个请求。
漏桶的话,99个请求可能一直在桶里面呆着,6个请求可能就被抛弃了,当然也可以缓存起来,但是这样几乎就相当于扩大了桶的大小,总归是要有个限定的,而且,这6个请求要等待,等99个请求完全通过,才能进行处理,很容易发生超时的情况哦!
令牌桶的话,99个请求直接就会去处理,因为令牌桶是满的(相当于是积蓄),6个请求当然无法取到足够的令牌,要等一下(缓存或者阻塞),或者是抛弃请求,不过6个令牌很快就能生成,所以稍等一会就能完成处理。
在我看来,令牌桶就是一种有准备的方式,可以应对许多突发状况。我们知道,很多情况都是遵循二八原则,百分之二十的时间承受百分之八十的业务量。这个有准备很有必要的!
还有就是,漏桶,一旦请求过多,要么放大空间进行缓存,要么就是抛弃请求。而令牌桶中,请求获取到令牌(通行证)之后,直接就处理了,后续请求可以阻塞,抛弃,处理部分等等方式进行处理。
可以比漏桶,少一个桶的请求滞留!而且如果突发性间隔时间比较长的话,由于令牌桶的“有准备”,会比漏桶的速度快很多,这个就是性能方面的体现了。
写个代码简单实现下,既然是令牌,首先想到的肯定是semaphore,然后直接干就行了!
- /**
- * desc: 令牌桶算法
- *
- * @author zhang
- * @date 2021/11/18 11:39
- */
- public class TokenBucket {
-
- private static final Integer TOKEN_BUCKET = 100 ;
-
- private static final Integer SECOND_TOKEN = 3 ;
- // 取空两次在停止
- private static CountDownLatch countDownLatch = new CountDownLatch(1);
-
- public static void main(String[] args) throws InterruptedException {
- // 100 个令牌
- Semaphore semaphore = new Semaphore(TOKEN_BUCKET);
- //每秒放两个,简单处理,一个线程放
- //定时器
- ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
- // semaphore.acquire(50);
- //执行
- executor.scheduleAtFixedRate(()->{
- // 查询线程个数,不能超过 TOKEN_BUCKET
- int tokenNum = semaphore.availablePermits();
- //对比,放令牌呗
- if (tokenNum < TOKEN_BUCKET){
- //释放不就是重新放进去么?嘿嘿。当然最多放 SECOND_TOKEN 个,而且不能超过桶的大小。
- semaphore.release(Math.min(SECOND_TOKEN,TOKEN_BUCKET - tokenNum));
- System.out.println("放入后令牌数量:"+tokenNum);
- }
- },0,1, TimeUnit.NANOSECONDS);
-
- // 来个拿令牌的吧,主线程循环啦。。算了,开个线程吧,不是啥大事。
- ExecutorService dealExecutor = Executors.newFixedThreadPool(1);
- dealExecutor.execute(()->{
- for (int i = 0; i < 10000; i++) {
- //多获取几个令牌,可以快点停止
- boolean flag = semaphore.tryAcquire(2);
- // 获取不到足够令牌,或者剩余令牌数为0的话,就结束了
- if (!flag){
- executor.shutdownNow();
- countDownLatch.countDown();
- return;
- } else {
- System.out.println(System.currentTimeMillis() + Thread.currentThread().getName()+"现在剩余令牌:" + semaphore.availablePermits());
- if (semaphore.availablePermits() == 0){
- executor.shutdownNow();
- countDownLatch.countDown();
- return;
- }
- }
- }
- });
- countDownLatch.await();
- dealExecutor.shutdownNow();
- }
- }
简单说下吧,以上代码只是为了简单描述令牌桶实现的,放入令牌桶的频率有点高哈,而取令牌更是循环一直取,令牌为0或者取不到足够令牌(2个),就会退出程序。
看图可能有点不对劲是吧?最后咋出来个36? 还不是程序运行的太快了。。虽然是开了单独的线程,但是写日志,是写到一起的啊,几个纳秒的差距,日志顺序自然就不对了。如果要看的更清晰一些,可以在循环取令牌的时候,可以sleep一下,每次睡眠1秒,然后放入令牌也可以,那样日志应该是按照顺序来的了。
大致思想是这样,下次看Google实现的令牌桶,封装的就完善许多了,大伙有时间也可以自己看下哈,Guava-RateLimiter !!就是这个东东哈
no sacrifice,no victory~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。