限流之令牌桶算法(一)

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

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

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

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

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

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

         

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

        比如,桶的大小为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~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
令牌桶算法是一种常见的限流算法,它可以控制请求的速率,防止系统被过多的请求压垮。下面是Java实现令牌桶算法的步骤和代码逻辑: 1. 定义一个令牌桶类,包含以下属性: - 最后一次令牌发放时间 - 桶的容量 - 令牌生成速度 - 当前令牌数量 2. 实现一个获取令牌的方法,该方法会在每次请求到来时被调用,具体实现如下: - 计算当前令牌数量 - 判断当前令牌数量是否足够 - 如果令牌数量不足,则拒绝请求 - 如果令牌数量足够,则领取令牌,并执行业务逻辑 3. 使用ScheduledExecutorService定时生成令牌,具体实现如下: - 每隔一段时间生成一定数量的令牌 - 如果令牌数量超过桶的容量,则不再生成令牌 下面是Java实现令牌桶算法的代码逻辑: ``` @Slf4j public class TokensLimiter { private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); // 最后一次令牌发放时间 public long timeStamp = System.currentTimeMillis(); // 桶的容量 public int capacity = 10; // 令牌生成速度10/s public int rate = 10; // 当前令牌数量 public int tokens; public void acquire() { scheduledExecutorService.scheduleWithFixedDelay(() -> { long now = System.currentTimeMillis(); // 当前令牌数 tokens = Math.min(capacity, (int) (tokens + (now - timeStamp) * rate / 1000)); // 每隔0.5秒发送随机数量的请求 int permits = (int) (Math.random() * 9) + 1; log.info("请求令牌数:" + permits + ",当前令牌数:" + tokens); timeStamp = now; if (tokens < permits) { // 若不到令牌,拒绝 log.info("限流了"); } else { // 还有令牌,领取令牌 tokens -= permits; log.info("剩余令牌=" + tokens); } }, 1000, 500, TimeUnit.MILLISECONDS); } public static void main(String[] args) { TokensLimiter tokensLimiter = new TokensLimiter(); tokensLimiter.acquire(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笔下天地宽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值