Bucket4j-初步了解

简介

令牌桶是一种限速算法,与之相对的是漏桶

  • 令牌限速

当进行任务的操作时,消耗一定的令牌,后台以一定的速率生产令牌。

在没有令牌的情况下,就阻塞任务,或者拒绝服务。

令牌的生产速率,代表了大部分情况下的平均流速。

  • 桶限峰值

的作用就是存储令牌,消耗的令牌都是从中获取。

桶的作用是用来限制流速的峰值,当桶中有额外令牌的时候,实际的流速就会高于限定的令牌生产速率。

假设令牌生产速率为v,桶大小为b,处理时间为t,则实际流量速度为V=v+b/t

  • 额外消耗

为了保证功能的完整,后台必须保证令牌生产,而且是持续服务,不能中断。

同时,为了功能的正确作用,当桶满了以后,后续生产的令牌会溢出,不会存储到桶内部。

使用

基本使用

  1. 消费
public static void main(String[] args) {
        Bandwidth limit = Bandwidth.simple(10, Duration.ofSeconds(1));
        Bucket bucket = Bucket4j.builder().addLimit(limit).build();
        if(bucket.tryConsume(1)){
            System.out.println("do something");
        }else{
            System.out.println("do nothing");
        }
    }

Bandwidth:带宽,也就是每秒能够通过的流量,自动维护令牌生产。

Bucket:桶,不论状态,或是令牌的消费,bucket是我们操作的入口。

tryConsume:尝试消费n个令牌,返回布尔值,表示能够消费或者不能够消费,给我们判断依据。

为了简单理解可以尝试一下如下代码。

public static void main(String[] args) {
        Bandwidth limit = Bandwidth.simple(1, Duration.ofSeconds(1));
        Bucket bucket = Bucket4j.builder().addLimit(limit).build();
        while(true){
            if(bucket.tryConsume(1)){
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }else{
                try{
                    System.out.println("waiting...");
                    Thread.sleep(200);
                }catch (Exception e){
                }
            }
        }
    }
  1. 阻塞
    public static void main(String[] args) throws InterruptedException {
        Bandwidth limit = Bandwidth.simple(1, Duration.ofSeconds(1));
        Bucket bucket = Bucket4j.builder().addLimit(limit).build();
        while(true){
            bucket.asScheduler().consume(1);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }
    }

运行一下就能够了解。

asScheduler会进行阻塞,直到获取令牌才进行后续语句的执行。

  1. 探针
public static void main(String[] args) throws InterruptedException {
        Bandwidth limit = Bandwidth.simple(5, Duration.ofSeconds(1));
        Bucket bucket = Bucket4j.builder().addLimit(limit).build();
        while(true){
            ConsumptionProbe probe = bucket.tryConsumeAndReturnRemaining(1);
            if(probe.isConsumed()){
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date())
                        +"\t剩余令牌: "+ probe.getRemainingTokens());
            }else{
                System.out.println("waiting...");
                Thread.sleep(200);
            }
        }
    }

tryConsumeAndReturnRemaining:获取探针

isConsumed:判断是否能消耗

getRemainingTokens:查询剩余令牌数量

  1. 桶控制
public static void main(String[] args) throws InterruptedException {
        long bucketSize = 9;
        Refill filler = Refill.greedy(2, Duration.ofSeconds(1));
        Bandwidth limit = Bandwidth.classic(bucketSize, filler);
        Bucket bucket = Bucket4j.builder().addLimit(limit).build();
        while(true){
            ConsumptionProbe probe = bucket.tryConsumeAndReturnRemaining(1);
            if(probe.isConsumed()){
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date())
                        +"\t剩余令牌: "+ probe.getRemainingTokens());
            }else{
                Thread.sleep(2000);
            }
        }
    }

Refiller:填充速度

bucketSize:桶容量

classic:用Refiller创建Bandwidth

可以观察到初始容量有10个,休眠两秒,每次消耗刚好四个,验证Refill功能。

稍微深入

  1. 初始化令牌数量
public static void main(String[] args) throws InterruptedException {
        long bucketSize = 9;
        Refill filler = Refill.greedy(2, Duration.ofSeconds(1));
        Bandwidth limit = Bandwidth.classic(bucketSize, filler).withInitialTokens(5);
        Bucket bucket = Bucket4j.builder().addLimit(limit).build();
        while(true){
            ConsumptionProbe probe = bucket.tryConsumeAndReturnRemaining(1);
            if(probe.isConsumed()){
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date())
                        +"\t剩余令牌: "+ probe.getRemainingTokens());
            }else{
                Thread.sleep(2000);
            }
        }
    }

withInitialTokens:桶初始化令牌数。

一般情况下,桶容量初始化时默认是满的,可以设置初始化时桶内的令牌数。

  1. 杜绝贪婪
public static void main(String[] args) throws InterruptedException {
        long bucketSize = 10;
        Refill filler = Refill.greedy(10, Duration.ofSeconds(1));
        Bandwidth limit = Bandwidth.classic(bucketSize, filler).withInitialTokens(0);
        Bucket bucket = Bucket4j.builder()
                .addLimit(limit)
                .build();
        while(true){
            if(bucket.tryConsume(1)){
                System.out.println(new SimpleDateFormat("HH:mm:ss:SSS").format(new Date()));
            }
        }
    }

运行一下,就能够大致了解Refill的贪婪了。

它总是那么急,我们的10/s,施行下去结果变成了1/ms

如果我们必须是瞬间生成10个,这就违背我们的意愿了。

Refill filler = Refill.intervally(10, Duration.ofSeconds(1));

这样,我们的意愿就不会因为贪婪而有所扭曲了。

  1. 手动添加
bucket.addTokens(500);

不等创建,自己手动添加,适用于回滚。

public static void main(String[] args) throws InterruptedException {
        long bucketSize = 10;
        Refill filler = Refill.intervally(10, Duration.ofSeconds(1));
        Bandwidth limit = Bandwidth.classic(bucketSize, filler).withInitialTokens(0);
        Bucket bucket = Bucket4j.builder()
                .addLimit(limit)
                .build();
        bucket.addTokens(500);
        while(true){
            if(bucket.tryConsume(1)){
                try{
                    throw new Exception("create a Exception");
                } catch (Exception e) {
                    bucket.addTokens(1);
                }
            }
        }
    }
  1. 时钟
  1. 粒度

从上面的贪婪,可以发现,Bucket4j的时钟是以毫秒进行衡量的,如果想要微秒之类的更细粒度操控,需要自己设置

// 毫秒
Bucket4j.builder().withMillisecondPrecision().build;
// 微秒
Bucket4j.builder().withNanosecondPrecision().build()

withMillisecondPrecision:毫秒

withNanosecondPrecision:微秒

担心系统时钟不准,可以采用自己的时钟。

Bucket4j.builder().withCustomTimePrecision(new MyClock()).build()

withCustomTimePrecision:指定时钟

  1. 配置热替换
BucketConfiguration config = Bucket4j.configurationBuilder()
    .addLimit(Bandwidth.simple(1, Duration.ofSeconds(2)))
    .build();
bucket.replaceConfiguration(config);

时机到了,就可以更换配置。

业务繁忙的时候令牌带宽可以适当的放宽,没有请求的时候也可以适当的减缓令牌生成。

毕竟生成令牌也会占用资源不是。

  1. 波动令牌
Bucket bucket = Bucket4j.builder()
       .addLimit(Bandwidth.simple(1000, Duration.ofMinutes(1)))
       .addLimit(Bandwidth.simple(50, Duration.ofSeconds(1)))
       .build();

当添加多个BandWidth,都会生效。

考虑总体的满足,在情况允许的情况下会尽量满足全部的要求。

如例子,如果满足第二个条件,第一个条件必定会被打破,但是也不是一直不会满足第二个条件。

因此,在整体上不超过带宽,但是允许不全部占用的情况。

令牌的生成也不必时刻都是火力全开,为了满足全部的限制,有时候不得不消极怠工

这种波动,打破了平均生产的境况,从而允许动态速率的生成令牌。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值