当前位置:   article > 正文

限流之令牌桶算法(一)_令牌桶阻塞太多

令牌桶阻塞太多

        之前已经说过漏桶算法,请求过来之后,由漏桶来阻挡,最后将流量漏出去,达到限流的目的,削弱峰值流量对服务器的压力。

        不过,漏桶算法真正在生产中用的并不是很多,更多的还是使用令牌桶算法。什么是令牌桶算法呢?

        说的已经很清楚了么,令牌--桶。就是一个桶里面放了令牌(一个校验或者说是一个通行证),每次有请求过来,必须要从桶里拿到令牌(许可证),才允许通过。如果拿不到,那不好意思,你就不允许通过了。

        令牌是哪里来的呢?当然是定时生成的啊,就好像桶的那个漏洞一样,一直在滴水,而这个令牌桶,也一直在往里面令牌,直到令牌桶满了,才会停止加(生成)令牌。

        那令牌桶和漏桶有啥区别呢?都是通过一个桶进行限流,漏桶似乎更方便,而且避免了生成令牌这个步骤,不是更简单么?

        这个也想了一下其中的区别,当然度娘的帮助更大!

         

        简单的说下,漏桶算法,其实太匀速,太平缓了,对于一些突发状况很难进行处理。

        比如,桶的大小为100,突然来了99个请求,然后又来了6个请求。

        漏桶的话,99个请求可能一直在桶里面呆着,6个请求可能就被抛弃了,当然也可以缓存起来,但是这样几乎就相当于扩大了桶的大小,总归是要有个限定的,而且,这6个请求要等待,等99个请求完全通过,才能进行处理,很容易发生超时的情况哦!

        令牌桶的话,99个请求直接就会去处理,因为令牌桶是满的(相当于是积蓄),6个请求当然无法取到足够的令牌,要等一下(缓存或者阻塞),或者是抛弃请求,不过6个令牌很快就能生成,所以稍等一会就能完成处理。

        在我看来,令牌桶就是一种有准备的方式,可以应对许多突发状况。我们知道,很多情况都是遵循二八原则,百分之二十的时间承受百分之八十的业务量。这个有准备很有必要的!

        还有就是,漏桶,一旦请求过多,要么放大空间进行缓存,要么就是抛弃请求。而令牌桶中,请求获取到令牌(通行证)之后,直接就处理了,后续请求可以阻塞,抛弃,处理部分等等方式进行处理。

        可以比漏桶,少一个桶的请求滞留!而且如果突发性间隔时间比较长的话,由于令牌桶的“有准备”,会比漏桶的速度快很多,这个就是性能方面的体现了。

写个代码简单实现下,既然是令牌,首先想到的肯定是semaphore,然后直接干就行了!

  1. /**
  2. * desc: 令牌桶算法
  3. *
  4. * @author zhang
  5. * @date 2021/11/18 11:39
  6. */
  7. public class TokenBucket {
  8. private static final Integer TOKEN_BUCKET = 100 ;
  9. private static final Integer SECOND_TOKEN = 3 ;
  10. // 取空两次在停止
  11. private static CountDownLatch countDownLatch = new CountDownLatch(1);
  12. public static void main(String[] args) throws InterruptedException {
  13. // 100 个令牌
  14. Semaphore semaphore = new Semaphore(TOKEN_BUCKET);
  15. //每秒放两个,简单处理,一个线程放
  16. //定时器
  17. ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
  18. // semaphore.acquire(50);
  19. //执行
  20. executor.scheduleAtFixedRate(()->{
  21. // 查询线程个数,不能超过 TOKEN_BUCKET
  22. int tokenNum = semaphore.availablePermits();
  23. //对比,放令牌呗
  24. if (tokenNum < TOKEN_BUCKET){
  25. //释放不就是重新放进去么?嘿嘿。当然最多放 SECOND_TOKEN 个,而且不能超过桶的大小。
  26. semaphore.release(Math.min(SECOND_TOKEN,TOKEN_BUCKET - tokenNum));
  27. System.out.println("放入后令牌数量:"+tokenNum);
  28. }
  29. },0,1, TimeUnit.NANOSECONDS);
  30. // 来个拿令牌的吧,主线程循环啦。。算了,开个线程吧,不是啥大事。
  31. ExecutorService dealExecutor = Executors.newFixedThreadPool(1);
  32. dealExecutor.execute(()->{
  33. for (int i = 0; i < 10000; i++) {
  34. //多获取几个令牌,可以快点停止
  35. boolean flag = semaphore.tryAcquire(2);
  36. // 获取不到足够令牌,或者剩余令牌数为0的话,就结束了
  37. if (!flag){
  38. executor.shutdownNow();
  39. countDownLatch.countDown();
  40. return;
  41. } else {
  42. System.out.println(System.currentTimeMillis() + Thread.currentThread().getName()+"现在剩余令牌:" + semaphore.availablePermits());
  43. if (semaphore.availablePermits() == 0){
  44. executor.shutdownNow();
  45. countDownLatch.countDown();
  46. return;
  47. }
  48. }
  49. }
  50. });
  51. countDownLatch.await();
  52. dealExecutor.shutdownNow();
  53. }
  54. }

简单说下吧,以上代码只是为了简单描述令牌桶实现的,放入令牌桶的频率有点高哈,而取令牌更是循环一直取,令牌为0或者取不到足够令牌(2个),就会退出程序。

看图可能有点不对劲是吧?最后咋出来个36? 还不是程序运行的太快了。。虽然是开了单独的线程,但是写日志,是写到一起的啊,几个纳秒的差距,日志顺序自然就不对了。如果要看的更清晰一些,可以在循环取令牌的时候,可以sleep一下,每次睡眠1秒,然后放入令牌也可以,那样日志应该是按照顺序来的了。

大致思想是这样,下次看Google实现的令牌桶,封装的就完善许多了,大伙有时间也可以自己看下哈,Guava-RateLimiter !!就是这个东东哈

        no sacrifice,no victory~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/728461
推荐阅读
相关标签
  

闽ICP备14008679号